AngularAtlased. 22.2

Cap. 03Signals & reactivitate14 / 75

effect() — când DA, când NU

Efecte secundare sincronizate cu semnale: localStorage, logging, API-uri imperative. Și capcanele lor.

stabil ~0 min de citit Documentația oficială

effect() rulează o funcție o dată, apoi din nou de fiecare dată când se schimbă un semnal pe care l-a citit. E puntea dintre lumea reactivă (semnale) și lumea care nu știe de semnale: DOM manual, localStorage, librării terțe, analytics, console.

Regula într-o frază

Un effect duce date din semnale spre exterior. Dacă îl folosești ca să setezi alt semnal, aproape sigur îți trebuia un computed() sau un linkedSignal().

Sintaxa

Creezi efectul într-un context de injecție (constructor sau inițializare de câmp). Efectul e legat automat de ciclul de viață al componentei sau serviciului: când acesta e distrus, și efectul e oprit.

import { Component, effect, signal } from '@angular/core';

@Component({ /* ... */ })
export class Settings {
  readonly theme = signal<'light' | 'dark'>('light');

  constructor() {
    // Rulează o dată la creare, apoi de fiecare dată când `theme` se schimbă.
    effect(() => {
      document.documentElement.dataset['theme'] = this.theme();
      localStorage.setItem('theme', this.theme());
    });
  }
}

Când rulează exact?

  • Nu sincron: dacă setezi un semnal de 3 ori la rând, efectul rulează o singură dată, cu valoarea finală.
  • Efectele de componentă rulează în timpul change detection, înainte ca template-ul componentei să fie verificat.
  • Efectele din servicii root rulează ca microtask-uri, înainte de change detection.
  • Dependențele sunt dinamice: contează doar semnalele citite la ultima rulare.
LiveAutosave în localStorage + textarea care crește singur
0 caractere · textarea-ul crește singur (afterRenderEffect)
Scrie, apoi reîncarcă pagina (F5): textul rămâne. effect() salvează, afterRenderEffect() măsoară DOM-ul.

Cleanup: onCleanup

Efectul primește o funcție onCleanup. Callback-ul înregistrat rulează înainte de următoarea execuție și la distrugere. E locul unde oprești timere, dezabonezi listener-e, anulezi request-uri.

effect((onCleanup) => {
  if (!this.running()) return;

  const id = setInterval(() => this.ticks.update((t) => t + 1), this.speed());

  // Se apelează înainte de următoarea rulare a efectului
  // și când componenta / serviciul e distrus.
  onCleanup(() => clearInterval(id));
});
LiveInterval controlat de semnale
0
  • Apasă „Pornește”. Schimbă intervalul în timp ce rulează.
Fiecare schimbare de interval oprește timer-ul vechi (cleanup) și pornește unul nou.

afterNextRender & afterRenderEffect: când îți trebuie DOM-ul

Dacă trebuie să măsori sau să modifici DOM-ul după ce Angular l-a actualizat, folosește hook-urile de randare. afterNextRender rulează o singură dată (perfect pentru a inițializa o librărie de grafice sau hărți), iar afterRenderEffect e un effect care rulează după randare, doar când semnalele lui s-au schimbat. Ambele rulează doar în browser (nu pe server la SSR).

import { afterNextRender, afterRenderEffect, ElementRef, viewChild } from '@angular/core';

export class AutoGrowTextarea {
  private readonly area = viewChild.required<ElementRef<HTMLTextAreaElement>>('area');
  readonly text = signal('');

  constructor() {
    // Rulează DUPĂ ce Angular a actualizat DOM-ul, ori de câte ori `text` se schimbă.
    afterRenderEffect(() => {
      this.text();
      const el = this.area().nativeElement;
      el.style.height = 'auto';
      el.style.height = `${el.scrollHeight}px`;
    });

    // O singură dată, după prima randare (ideal pentru librării care au nevoie de DOM).
    afterNextRender(() => {
      this.chart = new ThirdPartyChart(this.canvas().nativeElement);
    });
  }
}

// Cu faze explicite — eviți „layout thrashing” când ai multe componente:
afterRenderEffect({
  earlyRead: () => this.host.nativeElement.getBoundingClientRect().width, // citește DOM
  write: (width) => { this.panel().nativeElement.style.width = `${width()}px`; }, // scrie DOM
});

Anti-pattern-uri (și ce folosești în loc)

1. Sincronizarea între semnale

Așa
// ✓ computed pentru stare derivată
readonly fullName = computed(() => `${this.firstName()} ${this.lastName()}`);

// ✓ linkedSignal dacă valoarea derivată trebuie să fie și editabilă
readonly selected = linkedSignal(() => this.options()[0]);
Nu așa
// ✗ effect folosit ca să „sincronizeze” stare
readonly firstName = signal('Ana');
readonly lastName = signal('Pop');
readonly fullName = signal('');

constructor() {
  effect(() => this.fullName.set(`${this.firstName()} ${this.lastName()}`));
}
// Probleme: o rulare în plus, un moment în care fullName e vechi,
// și un graf de dependențe greu de urmărit.
LiveCitit imediat după set(): computed() vs o copie ținută la zi cu effect()
firstName.set(…)
computed()Ana Pop
effect() + set()Ana Poprulări ale efectului: 1

Pe ecran cele două arată la fel: efectul unei componente rulează la change detection, înainte ca template-ul ei să fie actualizat. Diferența apare în codul care citește valoarea imediat după set().

Citit în același handler de click, imediat după set()

  1. Apasă un nume: vezi ce întoarce fiecare variantă chiar în acel moment.
Fiecare nume apelează set() și apoi, în același handler de click, citește ambele valori. computed() se recalculează când îl citești; efectul rulează abia mai târziu, la change detection.

2. Fetch de date din effect

Așa
// ✓ resource / httpResource: anulare automată, status, error, reload
readonly user = httpResource<User>(() => `/api/users/${this.userId()}`);
Nu așa
// ✗ HTTP declanșat dintr-un effect
effect(() => {
  const id = this.userId();
  this.http.get<User>(`/api/users/${id}`).subscribe((u) => this.user.set(u));
  // fără anulare: răspunsurile vechi pot sosi după cele noi (race condition)
});

Contextul de injecție

effect() are nevoie de un injector ca să știe când să se distrugă. În afara constructorului primești eroarea NG0203. Soluția: creezi efectul ca field sau în constructor; în cazuri rare îi dai explicit injector.

export class Dashboard {
  private readonly injector = inject(Injector);

  // ✓ în field initializer sau constructor: context de injecție disponibil
  private readonly logEffect = effect(() => console.log(this.filter()));

  ngOnInit() {
    // ✗ aici nu mai ești în context de injecție → eroare NG0203
    // effect(() => ...);

    // ✓ dacă chiar trebuie mai târziu, dă-i explicit injectorul
    effect(() => console.log(this.filter()), { injector: this.injector });
  }

  stop() {
    this.logEffect.destroy(); // oprire manuală (rar nevoie — se distruge singur cu componenta)
  }
}

Înainte să scrii un effect, verifică dacă nu e de fapt stare derivată (signal() și computed()) sau stare care se resetează (linkedSignal() — stare derivată editabilă). De ce rulează un effect exact când rulează afli la Semnalele pe dinăuntru.

Când DA, când NU

Când DA
  • Persistare: localStorage, sessionStorage, IndexedDB.
  • Logging, analytics, telemetrie.
  • Librării imperative: grafice (Chart.js), hărți (Leaflet), editoare, canvas.
  • API-uri de browser: document.title, focus, scroll, clase pe <html>.
  • Sincronizare cu cod non-Angular (un store vechi, un widget jQuery).
Când NU
  • Stare derivată → computed().
  • Stare derivată editabilă / resetabilă → linkedSignal().
  • Request-uri la schimbarea unui parametru → resource() / httpResource().
  • Reacție la un click → pune logica direct în handler-ul evenimentului.
  • Validări de formular → Signal Forms au propriile reguli.

Capcane frecvente

  • Bucle infinite: un efect care citește și scrie același semnal se poate re-declanșa la nesfârșit. Folosește untracked() pentru citirea valorii curente sau, mai bine, regândește fluxul.
  • Dependențe ascunse: dacă apelezi o metodă care citește semnale în interior, și ele devin dependențe. Împachetează apelul în untracked(() => ...).
  • Așteptarea rulării sincrone: după signal.set() efectul NU a rulat încă. Nu scrie cod care depinde de asta.
  • effect în ngOnInit: eroare de context de injecție. Mută-l în constructor.

Fișă rapidă

effect(() => { /* citește semnale, face efecte secundare */ });
effect((onCleanup) => { const id = setInterval(...); onCleanup(() => clearInterval(id)); });
effect(fn, { injector, manualCleanup: true, debugName: 'sync-storage' });
const ref = effect(fn); ref.destroy();

afterNextRender(() => { /* o dată, după prima randare — init librării DOM */ });
afterRenderEffect(() => { /* după fiecare randare în care semnalele citite s-au schimbat */ });
afterRenderEffect({ earlyRead, write, mixedReadWrite, read });

// Regula: effect = „lumea reactivă → lumea non-reactivă”. Niciodată semnal → semnal.

Cap. 03 · 0 din 8 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. signal() și computed()
  2. effect() — când DA, când NU
  3. linkedSignal() — stare derivată editabilă
  4. resource, rxResource, httpResource
  5. debounced() — semnale cu întârziere
  6. Liste: căutare, filtrare, sortare, paginare
  7. Observables de la zero
  8. Signals ↔ RxJS
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