AngularAtlased. 22.2

Cap. 15Timp real și offline67 / 75

Offline-first: Service Worker și cache

Service Worker-ul Angular: strategii de cache, anunțarea versiunilor noi și o coadă pentru modificările făcute offline.

stabil ~0 min de citit Documentația oficială

O aplicație web are de obicei nevoie de rețea pentru orice, inclusiv ca să se afișeze. Un Service Worker stă între aplicație și rețea: poate răspunde dintr-un cache, poate încerca rețeaua cu o limită de timp sau amândouă. Bine făcut, aplicația se deschide instant într-un tren lent și continuă să meargă într-un tunel.

Angular are propriul Service Worker, condus de un fișier de configurare în loc de cod. Pagina asta acoperă ambele jumătăți: citirea (ce arăți când rețeaua e lentă sau lipsește) și scrierea (ce faci cu modificările făcute offline).

De ce contează
Offline nu e un caz rar: lifturi, trenuri, Wi-Fi de hotel, o liniuță de semnal. Iar o rețea lentă e mai rea decât niciuna: cererea nu pică, doar atârnă. Un timeout care cade pe cache transformă un spinner de 30 de secunde într-un răspuns în trei.

Cum îl pornești

ng add @angular/pwa adaugă pachetul @angular/service-worker, un manifest, iconițe, un ngsw-config.json și provideServiceWorker(). Worker-ul rulează doar în build-urile de producție și pe HTTPS (localhost e excepția). registerWhenStable:30000 îl înregistrează când aplicația devine stabilă sau după cel mult 30 de secunde, ca descărcările lui să nu concureze cu primul render.

ng add @angular/pwa
# adaugă @angular/service-worker, ngsw-config.json, un manifest, iconițe și provideServiceWorker()
ng build   # worker-ul rulează doar în build-urile de producție, pe HTTPS (sau pe localhost)

Ce intră în cache: assetGroups și dataGroups

assetGroups pun în cache fișierele aplicației. prefetch descarcă totul la instalare — shell-ul: index.html, JS, CSS — ca aplicația să se deschidă offline. lazy pune un fișier în cache doar prima dată când e cerut: imagini, fonturi, pagini folosite rar. Fișierele cu hash în nume nu se învechesc niciodată; worker-ul verifică dacă există o versiune nouă la fiecare navigare.

dataGroups pun în cache răspunsurile API-ului tău, cu una din două strategii. freshness întreabă întâi rețeaua și cade pe cache dacă rețeaua pică sau e mai lentă decât timeout: potrivit pentru date care se schimbă (prețuri, stoc, notificări). performance răspunde întâi din cache și merge la rețea doar când nu există o copie; cu refreshAhead, o copie care ajunge atât de aproape de maxAge e tot afișată, dar e reîmprospătată în fundal înainte să expire: potrivit pentru date care se schimbă rar (un catalog, setări, traduceri).

{
  "$schema": "./node_modules/@angular/service-worker/config/schema.json",
  "index": "/index.html",
  "assetGroups": [
    {
      "name": "app-shell",
      "installMode": "prefetch",
      "resources": { "files": ["/favicon.ico", "/index.html", "/*.css", "/*.js"] }
    },
    {
      "name": "assets",
      "installMode": "lazy",
      "updateMode": "prefetch",
      "resources": { "files": ["/assets/**", "/*.(svg|png|webp|woff2)"] }
    }
  ],
  "dataGroups": [
    {
      "name": "prices",
      "urls": ["/api/prices/**"],
      "cacheConfig": { "strategy": "freshness", "timeout": "3s", "maxSize": 50, "maxAge": "1h" }
    },
    {
      "name": "catalogue",
      "urls": ["/api/products/**"],
      "cacheConfig": {
        "strategy": "performance",
        "maxSize": 200,
        "maxAge": "7d",
        "refreshAhead": "1d"
      }
    }
  ]
}
LiveAceeași cerere: trei strategii, trei rețele
Strategie
Rețea
În cache
  1. 0 ms cerere trimisă la rețea
  2. 3000 ms timeout după 3 s → răspuns din cache
  3. 6000 ms rețeaua răspunde târziu: cache-ul e actualizat pentru data viitoare
conținut după
3000 ms
sursă
cache
vechimea datelor
10 minute
Alege o rețea lentă: fără worker aștepți șase secunde, cu freshness trei, cu performance câteva milisecunde. Apoi treci offline cu cache-ul gol.

Atenție la maxAge: e o limită fermă, nu o sugestie. Intrările mai vechi de atât se aruncă, chiar și când nu există rețea — o copie expirată nu e afișată niciodată, offline sau nu. Alege-l după cele mai vechi date pe care ai mai fi dispus să le arăți.

Versiuni noi: întrebi, nu schimbi codul sub picioarele utilizatorului

Când faci deploy, worker-ul observă la următoarea navigare și descarcă versiunea nouă în fundal. Tabul deschis continuă să ruleze versiunea veche — amestecul a două versiuni e ce strică aplicațiile. SwUpdate.versionUpdates emite VERSION_READY când versiunea nouă e completă; arată un mesaj și reîncarcă atunci când utilizatorul e de acord. unrecoverable înseamnă că fișierele versiunii care rulează nu mai sunt pe server: reîncarcă. Un tab care stă deschis zile întregi ar trebui să apeleze și checkForUpdate() din când în când.

import { ApplicationRef, Component, DestroyRef, inject, signal } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { SwUpdate, type VersionReadyEvent } from '@angular/service-worker';
import { filter, interval } from 'rxjs';

@Component({
  selector: 'app-update-banner',
  template: `
    @if (ready()) {
      <div role="status">
        E gata o versiune nouă.
        <button type="button" (click)="reload()">Reîncarcă</button>
      </div>
    }
  `,
})
export class UpdateBanner {
  private readonly updates = inject(SwUpdate);
  protected readonly ready = signal(false);

  constructor() {
    if (!this.updates.isEnabled) return; // mod dev sau un browser fără Service Worker
    const destroyRef = inject(DestroyRef);

    this.updates.versionUpdates
      .pipe(
        filter((e): e is VersionReadyEvent => e.type === 'VERSION_READY'),
        takeUntilDestroyed(destroyRef),
      )
      .subscribe(() => this.ready.set(true)); // întrebăm; nu reîncărcăm în mijlocul unui formular

    // fișierele versiunii care rulează au dispărut de pe server: singura ieșire e un reload
    this.updates.unrecoverable
      .pipe(takeUntilDestroyed(destroyRef))
      .subscribe(() => document.location.reload());

    // un tab care stă deschis zile întregi trebuie și el să verifice din când în când
    const appRef = inject(ApplicationRef);
    void appRef.whenStable().then(() =>
      interval(6 * 60 * 60 * 1000)
        .pipe(takeUntilDestroyed(destroyRef))
        .subscribe(() => void this.updates.checkForUpdate()),
    );
  }

  protected reload(): void {
    document.location.reload(); // versiunea nouă pornește la următoarea încărcare a paginii
  }
}
Cache-ul din fața cache-ului
ngsw.json și ngsw-worker.js nu au voie să fie ținute mult în cache de CDN sau de server (Cache-Control: no-cache). Dacă CDN-ul păstrează un ngsw.json vechi, worker-ul nu află niciodată că există o versiune nouă, iar utilizatorii rămân pe cea veche cât trăiește cache-ul CDN-ului. Fișierele cu hash în nume, în schimb, pot fi immutable.

Scrieri offline: un outbox

Service Worker-ul pune în cache citirile; nu îți păstrează cererile POST pentru mai târziu. Modificările făcute offline merg într-un outbox: sunt salvate local, afișate ca în așteptare și pleacă una câte una, în ordine, când revine conexiunea.

LiveNotițe salvate offline
online conexiunea ta reală: online
  • Notițele salvate apar aici, cu starea sincronizării.
în așteptare
0
sincronizate
0
Treci offline, salvează două notițe, apoi revino online: pleacă în ordine. Demo-ul reacționează și la conexiunea ta reală — oprește și pornește Wi-Fi-ul.

Trei detalii fac un outbox de încredere. E persistat (aici în localStorage; IndexedDB pentru date mari sau dacă trebuie să-l citească un worker), așa că un reload nu pierde nimic. Trimite câte un element pe rând, așa că serverul primește modificările în ordinea în care au fost făcute. Și fiecare element poartă un Idempotency-Key generat când utilizatorul a salvat, așa că o trimitere reușită pe jumătate nu poate crea un duplicat — aceeași idee ca la actualizările optimiste. La navigator.onLine, doar false e sigur: true înseamnă că există o rețea, nu că ajunge la internet.

import { HttpClient, HttpContext } from '@angular/common/http';
import { DOCUMENT, Service, computed, effect, inject, signal, untracked } from '@angular/core';
import { firstValueFrom } from 'rxjs';
import { IDEMPOTENCY_KEY } from './idempotency'; // vezi pagina despre actualizări optimiste
import { injectOnline } from './online';

interface Pending {
  key: string; // Idempotency-Key, generată O SINGURĂ DATĂ, când utilizatorul salvează
  url: string;
  body: unknown;
}

const STORAGE = 'outbox';

@Service()
export class Outbox {
  private readonly http = inject(HttpClient);
  private readonly storage = inject(DOCUMENT).defaultView?.localStorage; // undefined în timpul SSR
  readonly online = injectOnline();
  readonly items = signal<readonly Pending[]>(JSON.parse(this.storage?.getItem(STORAGE) ?? '[]'));
  readonly waiting = computed(() => this.items().length);
  private flushing = false;

  constructor() {
    // rezistă la reload (IndexedDB dacă datele sunt mari sau trebuie citite dintr-un worker)
    effect(() => this.storage?.setItem(STORAGE, JSON.stringify(this.items())));
    effect(() => {
      if (this.online()) untracked(() => void this.flush());
    });
  }

  save(url: string, body: unknown): void {
    this.items.update((list) => [...list, { key: crypto.randomUUID(), url, body }]);
    if (this.online()) void this.flush();
  }

  /** Câte una, în ordine. Un eșec oprește coada; următorul "online" o reia. */
  private async flush(): Promise<void> {
    if (this.flushing) return;
    this.flushing = true;
    try {
      while (this.online() && this.items().length) {
        const [next] = this.items();
        await firstValueFrom(
          this.http.post(next.url, next.body, {
            context: new HttpContext().set(IDEMPOTENCY_KEY, next.key),
          }),
        );
        this.items.update((list) => list.slice(1));
      }
    } catch {
      // tot offline sau serverul e căzut: elementul rămâne primul la rând
    } finally {
      this.flushing = false;
    }
  }
}

Când DA, când NU

Când DA
  • Aplicații folosite pe teren sau din mers: inspecții, livrări, vizite de vânzări.
  • Conținut pe care oamenii îl recitesc: documentație, articole, cataloage.
  • Formulare care nu au voie să piardă date când cade rețeaua.
Când NU
  • Date periculoase când sunt vechi: tranzacționare pe bursă, dispecerat live, informații de urgență.
  • Unelte interne folosite mereu pe rețeaua rapidă a biroului: complexitatea în plus aduce puțin.
  • Aplicații în care nu poți investi în invalidarea cache-ului și în testare: măcar pune în cache doar shell-ul.

Capcane frecvente

  • Un CDN care pune ngsw.json în cache. Utilizatorii rămân pe versiunea veche mult după deploy.
  • performance pentru date care trebuie să fie proaspete. Un preț de ieri, afișat instant și cu toată încrederea.
  • Reload automat când e gata o versiune. Utilizatorul pierde formularul pe care îl completa.
  • Încredere în navigator.onLine === true. Și Wi-Fi-ul de hotel cu pagină de login spune „online”.
  • Un outbox doar în memorie sau fără chei de idempotență. Primul pierde date la reload, al doilea creează duplicate la reîncercare.

Fișă rapidă

ng add @angular/pwa              worker + manifest + ngsw-config.json (doar build-uri de producție)
registrationStrategy             'registerWhenStable:30000': după primul render
assetGroups prefetch             shell-ul aplicației: index.html, JS, CSS, disponibile offline
assetGroups lazy                 imagini, fonturi: puse în cache la prima folosire
dataGroups freshness + timeout   întâi rețeaua; mai lent decât timeout → răspuns din cache
dataGroups performance           întâi cache-ul; refreshAhead reîmprospătează în fundal înainte de maxAge
maxAge                           limită fermă: intrările mai vechi se aruncă, chiar și offline
SwUpdate.versionUpdates          VERSION_READY → întrebi utilizatorul, reîncarci la click
SwUpdate.unrecoverable           fișierele versiunii vechi au dispărut → reload
checkForUpdate()                 periodic, pentru taburi care stau deschise zile întregi
CDN / cache HTTP                 ngsw.json și ngsw-worker.js: no-cache, niciodată immutable
navigator.onLine                 false e sigur, true înseamnă doar „există o rețea”
scrieri offline                  outbox: ordonat, persistat, fiecare cu un Idempotency-Key

Cap. 15 · 0 din 3 terminate

Ai terminat lecția?

Marcheaz-o și apare pe harta ta: în cuprins și pe prima pagină.

Se marchează singură după ce citești toată lecția.

  1. WebSockets: timp real care rezistă rețelei
  2. Actualizări optimiste și rollback
  3. Offline-first: Service Worker și cache
Vezi toată harta ta

Toate cele 75 de pagini · scrie ca să filtrezi

  • 01Ce e nou în Angular 21 & 22Toate schimbările importante din v21 și v22, pe scurt, cu link către pagina care le explică.Start
  • 01Prima ta aplicație, pas cu pasO listă de lecturi construită de la o componentă goală la o aplicație terminată — signals, @for, input-uri și output-uri, un serviciu și un formular, pas cu pas.Start
  • 01Decizii rapide„Unde pun asta?”, „Signal sau Observable?”, „Shared sau feature?” — răspunsuri scurte la întrebările de zi cu zi.Start
  • 01Întrebări de interviu Angular60 de întrebări reale de interviu pentru junior, mid și senior, cu răspunsul de dat, întrebarea care urmează și greșelile de evitat. Plus o simulare de interviu.Start
  • 02Standalone & bootstrapCum pornește o aplicație fără NgModule: bootstrapApplication, app.config.ts, imports în componentă.Fundamente moderne
  • 02Data binding și evenimenteInterpolare, binding pe proprietăți și pe atribute, evenimente cu $event și filtre de taste și cum e construit [( )] — ce intră în DOM și ce se întoarce.Fundamente moderne
  • 02Control flow: @if, @for, @switchSintaxa de blocuri care înlocuiește *ngIf/*ngFor/ngSwitch, cu track, @empty și verificare exhaustivă.Fundamente moderne
  • 02@let și expresiile noi din templateVariabile locale cu @let, arrow functions, spread, regex, instanceof, comentarii în tag-uri.Fundamente moderne
  • 02@defer — lazy loading în templateÎncarci componente grele doar când e nevoie: triggere, prefetch, placeholder, loading, error.Fundamente moderne
  • 02Class & style bindingsDe ce [class.x] și [style.x] au înlocuit ngClass și ngStyle (cu migrare automată din v21).Fundamente moderne
  • 02Stilurile componentelor și încapsularea:host, cele trei moduri de încapsulare, teme cu variabile CSS și de ce ::ng-deep e ultima soluție.Fundamente moderne
  • 02Pipes: când și cumPipe pur vs impur, pipe custom și cum alegi între pipe, computed() și o metodă.Fundamente moderne
  • 03signal() și computed()Primitivele reactive: stare scriibilă, stare derivată, egalitate, untracked și reguli de imutabilitate.Signals & reactivitate
  • 03effect() — când DA, când NUEfecte secundare sincronizate cu semnale: localStorage, logging, API-uri imperative. Și capcanele lor.Signals & reactivitate
  • 03linkedSignal() — stare derivată editabilăUn semnal care se resetează când se schimbă sursa, dar pe care îl poți și seta manual.Signals & reactivitate
  • 03resource, rxResource, httpResourceDate asincrone ca semnale: status, value, error, reload, anulare automată. Stabile din v22.Signals & reactivitate
  • 03debounced() — semnale cu întârziereVarianta „debounce” a unui semnal, fără RxJS. Perfect pentru căutare.Signals & reactivitate
  • 03Liste: căutare, filtrare, sortare, paginareUn lanț de computed-uri care recalculează doar ce s-a schimbat, cu filtrele ținute în URL, ca un link să redeschidă aceeași vedere.Signals & reactivitate
  • 03Observables de la zeroCe e un Observable, subscribe și unsubscribe, operatorii pe care îi folosești în fiecare săptămână, cold vs hot — și când ajunge un signal.Signals & reactivitate
  • 03Signals ↔ RxJStoSignal, toObservable, takeUntilDestroyed, outputFromObservable — și când RxJS rămâne alegerea bună.Signals & reactivitate
  • 04input() și output()Inputuri ca semnale (required, transform, alias) și outputuri tipizate. Data down, events up.Componente: comunicare
  • 04model() — two-way bindingBanana-in-a-box [( )] cu un singur API. Când îl folosești și când e mai bine input + output.Componente: comunicare
  • 04viewChild & contentChildInterogări ca semnale: viewChild, viewChildren, contentChild, contentChildren, opțiunea read.Componente: comunicare
  • 04Lifecycle & DestroyRefCe hook-uri mai contează în era signals, afterNextRender, DestroyRef și ngOnChanges tipizat.Componente: comunicare
  • 04Content projectionng-content cu select, sloturi multiple, conținut implicit și capcana proiecției condiționale.Componente: comunicare
  • 04ng-template & ngTemplateOutletTemplate-uri ca valori: render props în Angular, context tipizat și personalizare de componente.Componente: comunicare
  • 05Smart vs DumbContainer vs presentational: cine ține starea, cine doar afișează, și cum le împarți corect.Design de componente
  • 05Componente generice <T>Un DataTable<T> sau Select<T> tipizat corect, cu template-uri pentru celule.Design de componente
  • 05Componente dinamiceNgComponentOutlet, createComponent cu inputBinding/outputBinding și un dashboard construit din config.Design de componente
  • 05Directive & host compositionDirective atribut, metadata host: {} și Directive Composition API (hostDirectives).Design de componente
  • 05Controale custom de formularFormValueControl (Signal Forms) vs ControlValueAccessor: un rating cu stele, în ambele variante.Design de componente
  • 05Change detectionOnPush e implicit din v22, zone.js lipsește din v21. Ce declanșează randarea și cum să nu te arzi.Design de componente
  • 06inject() & @Service()Noul decorator @Service() din v22, inject() vs constructor și contextul de injecție.Dependency Injection
  • 06Providers & ierarhia de injectoriRoot vs rută vs componentă, useClass/useValue/useFactory/useExisting și modificatorii optional/self/skipSelf.Dependency Injection
  • 06InjectionToken & provideX()Configurare tipizată, funcții provideFeature(), makeEnvironmentProviders și inițializatori.Dependency Injection
  • 06injectAsync() — servicii lazyÎncarci un serviciu greu abia când ai nevoie de el, cu prefetch la idle.Dependency Injection
  • 07Structura de folderecore / shared / features, ce intră în fiecare și cum arată un feature pe dinăuntru.Arhitectura aplicației
  • 07Shared vs featureCând muți o componentă în shared și (mai ales) când NU. Checklist concret.Arhitectura aplicației
  • 07Interfețe & modeleInterfață inline sau fișier separat? DTO vs model de domeniu, type vs interface, unde le pui.Arhitectura aplicației
  • 07State management cu signalsStore-uri simple pe semnale, facade, stare locală vs globală și când îți trebuie NgRx.Arhitectura aplicației
  • 07Style guide & namingConvențiile oficiale actuale: nume de fișiere fără sufixe, protected, readonly, ordinea membrilor.Arhitectura aplicației
  • 07Barrels, aliasuri & granițeindex.ts, path aliases, API public pe feature și reguli de dependență care se pot verifica automat.Arhitectura aplicației
  • 07Erori: @boundary & ErrorHandlerNoul bloc @boundary din v22.2, ErrorHandler global, erori HTTP și erori din resource.Arhitectura aplicației
  • 08Routing modern & lazy loadingloadComponent, rute copil, parametri ca input-uri și moștenirea parametrilor (default nou în v22).Routing
  • 08Guards & resolverscanActivate, canMatch, canDeactivate și resolve ca funcții, cu redirect prin UrlTree / RedirectCommand.Routing
  • 09Signal Forms — bazeleFormulare construite pe semnale: form(), [formField], validare prin schemă. Stabile din v22.Formulare
  • 09Signal Forms — avansatValidare custom și cross-field, async/HTTP cu debounce, liste dinamice, disabled/hidden, scheme reutilizabile.Formulare
  • 09Reactive Forms tipizateFormGroup tipizat, nonNullable, FormArray — și cum conviețuiesc cu Signal Forms.Formulare
  • 10HttpClient, interceptors & API layerHttpClient fără setup (v21), Fetch implicit (v22), interceptori funcționali și stratul de API cu mapare DTO.HTTP & date
  • 11Performanță — checklistCe chiar contează: OnPush + signals, track, @defer, lazy routes, imagini, bundle și hidratare.Calitate & performanță
  • 11Testare cu VitestVitest e runner-ul implicit din v21. TestBed, componente cu semnale, servicii, HTTP și harness-uri.Calitate & performanță
  • 11DevTools: cum te uiți în aplicațieChrome DevTools și Angular DevTools pas cu pas: ce arată fiecare panou, cu exerciții care merg chiar pe site-ul ăsta.Calitate & performanță
  • 11Angular AriaComponente headless, accesibile, pe care le stilizezi cum vrei: tabs, accordion, menu, listbox...Calitate & performanță
  • 12SSR, prerender & render modesCând randezi pe server, când la build și când doar în browser: RenderMode pe rută, app.routes.server.ts și costurile reale.SSR & hidratare
  • 12Hidratare: event replay & incrementalCum preia browserul HTML-ul de pe server fără să-l refacă: hidratare completă, event replay, @defer (hydrate on …) și erorile de nepotrivire.SSR & hidratare
  • 12Cod compatibil cu serverulwindow nu există pe server: PLATFORM_ID, afterNextRender, DOCUMENT, TransferState, cache-ul HTTP și PendingTasks.SSR & hidratare
  • 13Micro frontendsCând merită să spargi o aplicație în mai multe deploy-uri și când e doar complexitate: Native Federation, Module Federation, iframes, Web Components.Arhitectură la scară
  • 13Web Components cu Angular ElementscreateCustomElement: o componentă Angular folosită ca tag HTML oriunde, plus comunicarea între aplicații prin evenimente și BroadcastChannel.Arhitectură la scară
  • 13Monorepo, librării & granițeUn workspace cu mai multe aplicații și librării: ng generate library, Nx, entry points secundare, publicare și reguli de dependență.Arhitectură la scară
  • 13Arhitectură hexagonală (ports & adapters)Domeniul fără Angular, porturi ca clase abstracte și adaptoare interschimbabile: HTTP, localStorage sau in-memory, fără să atingi UI-ul.Arhitectură la scară
  • 14Semnalele pe dinăuntruGraful reactiv: producători și consumatori, push-pull, de ce computed e lazy și glitch-free, problema diamantului și când rulează efectele.Reactivitate avansată
  • 14RxJS avansat: operatori de ordin superiorswitchMap, mergeMap, concatMap și exhaustMap pe o diagramă live, plus retry cu backoff, shareReplay și cum eviți scurgerile.Reactivitate avansată
  • 14NgRx SignalStoreState management la scară pe semnale: withState, withComputed, withMethods, rxMethod, entități și hooks — și când un store simplu e suficient.Reactivitate avansată
  • 14Mașini de stări cu signalsFără stări imposibile: o mașină tipizată pe signals, cu tranziții, guards și diagrama stărilor, live.Reactivitate avansată
  • 15WebSockets: timp real care rezistă rețeleiUn socket tipizat ca signals: reconectare cu backoff și jitter, heartbeat, coadă de mesaje cât ești offline și siguranță la SSR.Timp real și offline
  • 15Actualizări optimiste și rollbackInterfață instantanee, dar onestă: snapshot și rollback, răspunsuri sosite în altă ordine, chei de idempotență.Timp real și offline
  • 15Offline-first: Service Worker și cacheService Worker-ul Angular: strategii de cache, anunțarea versiunilor noi și o coadă pentru modificările făcute offline.Timp real și offline
  • 16Web WorkersCalcule grele fără să blochezi interfața: un worker real în pagină, comunicare prin mesaje și când nu merită.UI & performanță avansată
  • 16Liste uriașe: virtual scroll100.000 de rânduri fără lag: CDK virtual scroll, itemSize, trackBy și alternativele (paginare, infinite scroll).UI & performanță avansată
  • 16Animații moderne & View Transitionsanimate.enter și animate.leave în locul @angular/animations (depreciat), tranziții CSS și View Transitions la navigare.UI & performanță avansată
  • 16Memory leaks: cum le găsești și le repariSubscripții, timere și listeneri care trăiesc mai mult decât componenta lor — cum îi vezi în DevTools și cum îi închizi.UI & performanță avansată
  • 17Securitate: XSS, sanitizare & CSPCe face Angular automat împotriva XSS, când devine periculos bypassSecurityTrust, plus CSP cu nonce, Trusted Types și XSRF.Securitate & globalizare
  • 17Autentificare: token-uri & refreshUn interceptor care atașează token-ul, reîmprospătează o singură dată la 401 și reia cererile din coadă — plus unde ții token-ul.Securitate & globalizare
  • 17OAuth 2.1 și OpenID Connect cu PKCEFluxul authorization code cu PKCE, pas cu pas: verifier și challenge, state și nonce, ID token-ul și de ce un BFF e mai sigur.Securitate & globalizare
  • 17Internaționalizare (i18n)$localize, atributul i18n, ICU plural/select, build per limbă vs. traduceri la runtime și formatarea după locale.Securitate & globalizare