Change detection e procesul prin care Angular actualizează DOM-ul când se schimbă datele. Două schimbări mari din ultimele versiuni îți schimbă modul de lucru: în v21 aplicațiile noi sunt zoneless (fără Zone.js), iar în v22 componentele sunt OnPush implicit. Împreună înseamnă: Angular nu mai „ghicește” când s-a schimbat ceva — trebuie să-i spui, iar semnalele fac asta automat.
- v21: proiectele noi nu mai includ
zone.js. Change detection e programat de semnale, evenimente,markForCheck. - v22: componentele fără
changeDetectionsuntOnPush.ChangeDetectionStrategy.DefaultdevineEager(Default e deprecat). - ng update la v22 adaugă
Eagerautomat unde e nevoie, ca nimic să nu se strice.
OnPush implicit și Eager
// v22: fără `changeDetection` → OnPush
@Component({ selector: 'app-user-card', template: `...` })
export class UserCard {}
// Vrei comportamentul vechi („verifică-mă mereu”)? Explicit:
@Component({
selector: 'app-legacy-widget',
changeDetection: ChangeDetectionStrategy.Eager, // fostul Default (Default e deprecat)
template: `...`,
})
export class LegacyWidget {}Ce declanșează randarea (zoneless)
Ce programează change detection într-o aplicație zoneless:
✓ un signal citit în template se schimbă (signal.set/update, input(), resource, toSignal…)
✓ un event listener din template sau host: (click), (input), host: { '(keydown)': … }
✓ async pipe primește o valoare nouă
✓ ChangeDetectorRef.markForCheck()
✓ ComponentRef.setInput(), atașare / detașare de view-uri
✓ router: navigări
✗ setTimeout / setInterval / Promise.then care modifică un câmp simplu
✗ subscribe() care setează un câmp simplu
✗ callback-uri din librării terțe (WebSocket, Firebase, Maps) care setează câmpuri simple// ✗ Merge cu Zone.js, NU merge zoneless + OnPush
export class Clock {
now = new Date();
constructor() {
setInterval(() => (this.now = new Date()), 1000);
}
}
// ✓ Soluția idiomatică: semnal
export class Clock {
readonly now = signal(new Date());
constructor() {
const id = setInterval(() => this.now.set(new Date()), 1000);
inject(DestroyRef).onDestroy(() => clearInterval(id));
}
}
// ✓ Pentru Observable-uri: toSignal (sau async pipe)
readonly user = toSignal(this.userService.user$);
// Soluția de avarie pentru cod vechi: markForCheck
this.socket.on('msg', (m) => {
this.messages.push(m);
this.cdr.markForCheck();
});Regulile OnPush
O componentă OnPush e verificată doar când:
1. un input() al ei primește o REFERINȚĂ nouă (comparare cu ===)
2. un eveniment are loc în ea sau în copiii ei
3. un signal citit în template-ul ei se schimbă
4. async pipe din template emite
5. cineva apelează markForCheck() pe ea (sau pe un descendent)
Altfel, întreg sub-arborele ei e sărit. De aceea OnPush + semnale = randări minime.Mutații și OnPush
OnPush compară input-urile după referință. Dacă muți un obiect pe loc, copilul nu află. Exact aceeași regulă ca la semnale: imutabilitate.
ChangeDetectorRef: când chiar ai nevoie
const cdr = inject(ChangeDetectorRef);
cdr.markForCheck(); // „verifică-mă la următoarea rundă” (asincron, recomandat)
cdr.detectChanges(); // „verifică-mă ACUM, sincron” (rar: măsurători imediate, teste)
cdr.detach(); // scoate componenta din change detection (dashboard-uri cu update-uri manuale)
cdr.reattach();
// v22: cdr.checkNoChanges() a fost eliminat din API-ul public — în teste folosește fixture.detectChanges()Migrarea unui proiect existent
# ng update la v22 adaugă automat ChangeDetectionStrategy.Eager pe componentele
# care nu aveau changeDetection setat — comportamentul aplicației rămâne identic.
ng update @angular/core@22 @angular/cli@22
# Apoi, componentă cu componentă: scoți Eager, treci starea pe semnale, testezi.
# Pentru zoneless pe un proiect existent, Angular 21 a adăugat un tool de analiză:
# onpush_zoneless_migration (prin MCP server-ul Angular CLI: ng mcp)Semnalele sunt cele care spun ce trebuie randat din nou: signal() și computed() și, mai în profunzime, Semnalele pe dinăuntru. Ce face lentă o aplicație, pas cu pas, la Performanță — checklist.
Când DA, când NU
- Orice componentă nouă — e deja implicit.
- Starea e în semnale, input-uri, resources.
- Liste mari, dashboard-uri, pagini cu mult conținut.
Eager: cod vechi, încă nemigrat, care mută câmpuri simple din callback-uri.markForCheck: integrare cu librării terțe care nu știu de semnale (dar preferă să pui valoarea într-un semnal).detectChanges: rar — teste sau măsurători imediat după o schimbare.
Capcane frecvente
- „Merge doar după ce dau click undeva” = o valoare simplă modificată asincron. Treci-o pe semnal.
- Mutezi un obiect primit prin input → copilul OnPush nu se actualizează. Creează o referință nouă.
- Teste care se bazau pe Zone.js (
fakeAsync,whenStableimplicit): în zoneless foloseșteawait fixture.whenStable()și timere false din Vitest. - Librării vechi care presupun Zone.js (ex. care așteaptă
NgZone.onStable) — verifică compatibilitatea înainte să scoți zone.js.