AngularAtlased. 22.2

Cap. 15Timp real și offline65 / 75

WebSockets: timp real care rezistă rețelei

Un socket tipizat ca signals: reconectare cu backoff și jitter, heartbeat, coadă de mesaje cât ești offline și siguranță la SSR.

arhitectură ~0 min de citit

HTTP e o întrebare urmată de un răspuns. Un WebSocket e o linie care rămâne deschisă, iar serverul poate vorbi primul: un mesaj nou în chat, un preț care s-a schimbat, un build care s-a terminat. Deschiderea liniei e o singură linie de cod. Munca adevărată e să o ții în viață în tren, pe un laptop care intră în sleep sau cât serverul face un deploy.

Pagina construiește un socket tipizat care se reconectează singur și e expus ca signals: starea conexiunii, mesajele, un outbox pentru ce trimiți cât ești offline și o reconectare care nu dărâmă serverul din nou în clipa în care revine.

De ce contează
În producție, socket-urile mor tot timpul: Wi-Fi-ul schimbă rețeaua, proxy-urile închid conexiunile inactive după un minut, deploy-urile repornesc serverele. Un socket fără reconectare arată perfect în demo, apoi încetează în tăcere să se actualizeze în mâinile utilizatorului — fără nicio eroare pe ecran.

Minimul: webSocket() din RxJS

RxJS are un subject pentru WebSocket: subscribe() deschide conexiunea, next() trimite, unsubscribe() o închide. Implicit, mesajele sunt JSON în ambele direcții (serializer și deserializer schimbă asta).

import { webSocket } from 'rxjs/webSocket';

interface ChatMessage {
  type: 'chat';
  user: string;
  text: string;
}

const socket = webSocket<ChatMessage>('wss://api.example.com/live');

// subscribe() deschide conexiunea; fiecare mesaj vine deja parsat (JSON.parse)
const sub = socket.subscribe({
  next: (msg) => console.log(msg.user, msg.text),
  error: (err) => console.error('conexiune pierdută', err), // stream-ul SE TERMINĂ aici
  complete: () => console.log('închisă curat de server'), // ...sau aici
});

socket.next({ type: 'chat', user: 'ana', text: 'hi' }); // trimis ca JSON.stringify(...)

sub.unsubscribe(); // închide conexiunea

Două detalii contează mai târziu. Stream-ul se termină când cade conexiunea: cu error dacă a fost o cădere anormală, cu complete dacă serverul a închis-o curat. Iar ce trimiți înainte să fie deschisă conexiunea se păstrează și pleacă la conectare.

O conexiune care revine: backoff cu jitter

Când cade linia, cea mai proastă variantă e să reîncerci imediat, în buclă. Dacă serverul tocmai a repornit, toți clienții îl lovesc în aceeași secundă — thundering herd — și serverul cade din nou. Rețeta obișnuită: aștepți mai mult după fiecare eșec (backoff exponențial), pui un plafon și adaugi întâmplare (jitter), ca clienții să se împrăștie.

/**
 * „Full jitter”: o așteptare aleatoare între 0 și min(cap, base · 2^attempt).
 * încercarea 1 → 0–1 s, 2 → 0–2 s, 3 → 0–4 s ... niciodată peste 10 s.
 */
export function fullJitter(attempt: number, base = 500, cap = 10_000): number {
  return Math.random() * Math.min(cap, base * 2 ** attempt);
}
Live200 de clienți se reconectează după un restart al serverului
serverul revine 0 s 10 s 20 s 30 s 40 s

încercare eșuatăreconectat

vârf după restart
200
încercări în total
2200
reconectați
200 / 200
ultimul revine la
10.2 s
Compară cele trei strategii. Urmărește vârful: câți clienți lovesc serverul în aceeași jumătate de secundă, imediat după ce revine.

Intervalul fix de 1 s ciocănește serverul cât timp e căzut. Exponențialul fără jitter trimite mai puține cereri, dar în valuri sincronizate: toți cei care au eșuat în același moment reîncearcă în același moment. Full jitter împrăștie revenirile pe câteva secunde, așa că serverul primește un firicel în loc de un zid; prețul e că cei mai ghinioniști clienți revin cu câteva secunde mai târziu. Plafonul ține așteptarea în limite rezonabile. Pentru un socket, e aproape întotdeauna schimbul potrivit.

retry() nu ajunge: închiderile curate

retry() reacționează doar la erori. Un server care repornește politicos închide conexiunile cu codul 1001 sau 1012 („service restart”) și wasClean: true — iar atunci stream-ul se încheie, nu dă eroare. Fără repeat(), aplicația rămâne deconectată exact în timpul deploy-urilor. Resetează contorul de încercări în openObserver, când linia e chiar deschisă, nu la primul mesaj.

import { repeat, retry, timer } from 'rxjs';
import { webSocket } from 'rxjs/webSocket';
import { fullJitter } from './backoff';
import type { ServerMessage } from './live-socket';

let attempt = 0;
const wait = () => timer(fullJitter(++attempt));

const socket = webSocket<ServerMessage>({
  url: 'wss://api.example.com/live',
  openObserver: { next: () => (attempt = 0) }, // resetăm când linia e chiar deschisă
});

socket
  .pipe(
    retry({ delay: wait }), // eroare de rețea, închidere anormală (1006)
    repeat({ delay: wait }), // închidere CURATĂ (1001, 1012 „service restart”): stream-ul se încheie!
  )
  .subscribe((msg) => console.log(msg));
Bufferul pe care nu te poți baza
WebSocketSubject păstrează ce trimiți înainte să se deschidă socket-ul, dar înlocuiește bufferul la fiecare încercare eșuată (_resetState() în sursa RxJS). Mesajele trimise cât rețeaua e căzută se pierd imediat ce o încercare de reconectare eșuează. Dacă contează, ține-ți propriul outbox și golește-l în openObserver.

Socket-ul ca serviciu cu signals

Componentele n-ar trebui să știe de backoff sau de JSON. Un serviciu ține o singură conexiune pentru toată aplicația, expune starea și mesajele ca signals și oferă un singur send(). Cât linia e căzută, mesajele așteaptă într-un outbox limitat și pleacă în ordine după reconectare.

import { DestroyRef, Service, inject, signal } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { interval, repeat, retry, timeout, timer } from 'rxjs';
import { webSocket } from 'rxjs/webSocket';
import { fullJitter } from './backoff';

export type ServerMessage =
  | { type: 'chat'; user: string; text: string }
  | { type: 'presence'; online: number }
  | { type: 'pong' };
export type ClientMessage = { type: 'chat'; text: string } | { type: 'ping' };

export type SocketStatus = 'idle' | 'connecting' | 'open' | 'reconnecting';

const PING_EVERY = 15_000;
const DEAD_AFTER = 20_000; // niciun mesaj timp de 20 s = conexiune moartă

/** O singură conexiune pentru toată aplicația, expusă ca signals. */
@Service()
export class LiveSocket {
  private readonly destroyRef = inject(DestroyRef);

  readonly status = signal<SocketStatus>('idle');
  readonly online = signal(0);
  readonly messages = signal<readonly ServerMessage[]>([]);
  /** Ce s-a trimis cât linia a fost căzută; pleacă, în ordine, la următorul „open”. */
  readonly outbox = signal<readonly ClientMessage[]>([]);

  private attempt = 0;
  // RxJS folosește un singur tip pentru ambele direcții
  private readonly socket = webSocket<ServerMessage | ClientMessage>({
    url: 'wss://api.example.com/live',
    openObserver: { next: () => this.opened() },
    closeObserver: { next: () => this.status.set('reconnecting') },
  });

  connect(): void {
    if (this.status() !== 'idle') return;
    this.status.set('connecting');
    const wait = () => timer(fullJitter(++this.attempt));

    this.socket
      .pipe(
        timeout({ each: DEAD_AFTER }), // heartbeat: tăcere → eroare → retry
        retry({ delay: wait }),
        repeat({ delay: wait }),
        takeUntilDestroyed(this.destroyRef),
      )
      .subscribe((msg) => this.receive(msg as ServerMessage));

    interval(PING_EVERY)
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe(() => {
        if (this.status() === 'open') this.socket.next({ type: 'ping' });
      });
  }

  send(msg: ClientMessage): void {
    if (this.status() === 'open') this.socket.next(msg);
    else this.outbox.update((list) => [...list, msg].slice(-100)); // limitat!
  }

  private opened(): void {
    this.status.set('open');
    this.attempt = 0;
    const pending = this.outbox();
    this.outbox.set([]);
    pending.forEach((msg) => this.socket.next(msg));
  }

  private receive(msg: ServerMessage): void {
    if (msg.type === 'presence') this.online.set(msg.online);
    else if (msg.type === 'chat') this.messages.update((list) => [...list, msg].slice(-200));
  }
}
LiveO conexiune live: taie rețeaua, repornește serverul
neconectat0 online
  1. Scrie un mesaj: serverul îl trimite înapoi tuturor celor conectați.
  1. Evenimentele conexiunii apar aici.
Taie rețeaua, trimite două mesaje, apoi restabilește-o: pleacă în ordine după reconectare. Apoi încearcă „Cădere tăcută”: conexiunea rămâne deschisă, dar nu mai trece nimic.

Demo-ul rulează codul de mai sus, cu intervale mai scurte. Doar WebSocketCtor e înlocuit: în loc de un WebSocket adevărat primește un server în memorie, care poate reporni, poate pierde rețeaua sau poate îngheța o conexiune.

Heartbeat: o conexiune moartă care pare vie

O conexiune poate muri fără niciun eveniment de închidere: se închide capacul laptopului, un NAT uită maparea, rețeaua mobilă schimbă celula. TCP poate să nu observe minute întregi, iar readyState spune în continuare OPEN. Leacul e un heartbeat: clientul trimite un ping la interval fix, iar timeout({ each }) tratează orice tăcere mai lungă decât limita ca pe o linie moartă. Eroarea ajunge la retry(), care deschide o conexiune nouă.

Ce trimiți pe o linie moartă-dar-deschisă se pierde fără nicio eroare: send() reușește, datele nu ajung niciodată. Pentru mesajele care trebuie să ajungă (o comandă, o plată), dă-i fiecăruia un id, așteaptă un ack de la server și retrimite după reconectare ce n-a fost confirmat. Serverul ignoră duplicatele după id.

SSR, autentificare, taburi și resync

  • SSR și prerender. Codul rulează și pe server, unde nu există niciun socket care merită deschis. Conectează-te în afterNextRender (sau în spatele lui isPlatformBrowser) și lasă randarea de pe server să arate o stare goală. Vezi cod compatibil cu serverul.
  • Autentificare. Browserul nu te lasă să pui un header Authorization pe un WebSocket. Variante: un cookie de sesiune (același site), un ticket scurt, de unică folosință, luat prin HTTP și pus în URL, sau un prim mesaj auth. Nu pune un token cu viață lungă în URL — URL-urile ajung în logurile serverului. Token-urile și refresh-ul sunt în Autentificare: token-uri și refresh.
  • Mai multe taburi. Fiecare tab își deschide propria conexiune. Dacă asta e o problemă (limite de conexiuni, notificări duble), un singur tab poate deține socket-ul și le poate da celorlalte mesajele prin BroadcastChannel sau printr-un SharedWorker.
  • Resync după reconectare. Cât ai lipsit, serverul a trimis lucruri pe care nu le-ai primit. După fiecare open, cere ce ai pierdut (since=lastEventId) sau reîncarcă starea prin HTTP. Modificările făcute între timp sunt subiectul paginii despre actualizări optimiste.
import { HttpClient } from '@angular/common/http';
import { defer, retry, switchMap, timer } from 'rxjs';
import { webSocket } from 'rxjs/webSocket';
import { fullJitter } from './backoff';
import type { ServerMessage } from './live-socket';

// Browserul nu poate pune un header Authorization pe un WebSocket.
// 1) un ticket scurt prin HTTP (interceptorul de auth adaugă token-ul ca de obicei)
// 2) ticket-ul e de unică folosință și trăiește ~30 s, deci poate sta în URL
// 3) defer() → fiecare reconectare cere un ticket NOU
export function authenticatedSocket(http: HttpClient) {
  return defer(() => http.post<{ ticket: string }>('/api/ws-ticket', {})).pipe(
    switchMap(({ ticket }) => webSocket<ServerMessage>(`wss://api.example.com/live?ticket=${ticket}`)),
    retry({ delay: (_, n) => timer(fullJitter(n)) }),
  );
}

WebSocket sau Server-Sent Events?

Dacă vorbește doar serverul — notificări, un feed, progresul unui job, text trimis token cu token — Server-Sent Events sunt mai simple: HTTP obișnuit, reconectare inclusă în browser și id-ul ultimului eveniment trimis automat înapoi.

WebSocketServer-Sent Events
Direcțieîn ambele sensuriserver → browser
Protocolpropriu (ws:// / wss://, după un upgrade HTTP)HTTP obișnuit (text/event-stream)
Reconectareo scrii tuinclusă, cu Last-Event-ID
Datetext și binardoar text
Folosire tipicăchat, editare colaborativă, jocuri, tranzacționare în ambele sensurinotificări, feed-uri live, progresul unui job, streaming de text AI
import { Observable } from 'rxjs';

/** Server-Sent Events: doar server → browser, HTTP simplu, reconectare inclusă. */
export function serverEvents<T>(url: string): Observable<T> {
  return new Observable<T>((subscriber) => {
    const source = new EventSource(url, { withCredentials: true });
    source.onmessage = (e) => subscriber.next(JSON.parse(e.data) as T);
    source.onerror = () => {
      // browserul se reconectează singur (și trimite Last-Event-ID); doar CLOSED e definitiv
      if (source.readyState === EventSource.CLOSED) subscriber.error(new Error('SSE closed'));
    };
    return () => source.close();
  });
}

Când DA, când NU

WebSocket
  • Ambele părți vorbesc des: chat, prezență, editare colaborativă.
  • Latența contează, iar mesajele sunt mici și dese.
  • Ai nevoie de date binare sau de propriul protocol peste conexiune.
Altceva
  • Vorbește doar serverul: Server-Sent Events sunt mai simple. Cereri din când în când: HttpClient simplu.
  • Date care se schimbă la câteva minute: polling la 30–60 s e mai ieftin și mult mai simplu de întreținut.
  • Actualizări care trebuie să meargă offline și să se sincronizeze mai târziu: asta e o problemă offline-first, nu una de socket.

Capcane frecvente

  • Un socket per componentă. Cinci widget-uri, cinci conexiuni la același server. Ține conexiunea într-un serviciu root.
  • Retry fără jitter sau fără plafon. Mii de clienți revin în valuri sincronizate, sau un client așteaptă 17 minute după o pană lungă.
  • Doar retry(), fără repeat(). Închiderile curate (la fiecare deploy) termină stream-ul pentru totdeauna.
  • Încrederea în bufferul integrat. Mesajele trimise offline dispar după prima încercare eșuată. Folosește propriul outbox, limitat.
  • Fără heartbeat. Starea spune „conectat”, dar n-a mai venit nimic de zece minute.
  • Conectare în SSR, sau un JWT lung în URL. Primul deschide socket-uri de pe server; al doilea lasă token-uri în loguri.

Fișă rapidă

webSocket<T>(url | config)        subscribe = conectare · next() = trimitere · unsubscribe = închidere
înainte de primul „open”          next() e pus în buffer și trimis la conectare
...dar după o încercare eșuată    bufferul e aruncat → ține-ți propriul outbox
eroare de rețea / codul 1006      retry({ delay })
închidere curată (1000/1001/1012) repeat({ delay })   stream-ul SE ÎNCHEIE, nu dă eroare
backoff                           random(0, min(cap, base · 2^n))   „full jitter”
resetarea backoff-ului            în openObserver, nu la primul mesaj
moartă, dar neînchisă             ping la 15 s + timeout({ each: 20_000 })
SSR / prerender                   conectare în afterNextRender, niciodată în constructor
auth                              cookie · ticket scurt, de unică folosință · primul mesaj „auth”
după reconectare                  resync: cere ce ai pierdut (since=lastEventId)
mesaje care trebuie să ajungă     id + ack de la server, retrimite ce nu a fost confirmat
doar într-o direcție?             Server-Sent Events (EventSource): reconectarea e inclusă

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