Hook-urile de ciclu de viață (ngOnInit, ngOnChanges, ngOnDestroy...) există și funcționează în continuare. Dar cu semnale, cele mai multe devin inutile: derivarea din input-uri o face computed(), reacțiile le face effect(), lucrul cu DOM-ul îl fac afterNextRender(), iar curățenia o face DestroyRef.
Tabelul de echivalențe
Hook vechi → Echivalent modern
───────────────────────────────────────────────────────────────────
constructor → constructor (DI, semnale, effect, afterNextRender)
ngOnChanges → computed() / effect() / linkedSignal() pe input-uri
ngOnInit → computed() / resource() (sau ngOnInit, încă valid)
ngDoCheck → aproape niciodată nu ai nevoie
ngAfterContentInit → contentChild() / contentChildren() (semnale)
ngAfterViewInit → afterNextRender() + viewChild()
ngAfterViewChecked → afterEveryRender() (rar)
ngOnDestroy → inject(DestroyRef).onDestroy() / takeUntilDestroyed()Cum arată o componentă modernă
import { Component, DestroyRef, afterNextRender, computed, effect, inject, input } from '@angular/core';
@Component({ selector: 'app-user-panel', /* ... */ })
export class UserPanel {
readonly userId = input.required<number>();
private readonly api = inject(UserApi); // DI: în field-uri / constructor
// „ngOnChanges” → computed / effect / resource pe input-uri
readonly user = httpResource<User>(() => `/api/users/${this.userId()}`);
readonly title = computed(() => this.user.value()?.name ?? 'Se încarcă…');
constructor() {
// „ngAfterViewInit” → afterNextRender (o singură dată, după prima randare)
afterNextRender(() => this.chart.init(this.canvas().nativeElement));
// „ngOnDestroy” → DestroyRef.onDestroy (merge și în servicii, funcții, directive)
inject(DestroyRef).onDestroy(() => this.chart.dispose());
// reacție la schimbarea unui input
effect(() => this.analytics.track('user-panel-open', { id: this.userId() }));
}
}- Apasă „Creează copilul”.
ngOnChanges cu SimpleChanges tipizat v21
// Încă ai nevoie de ngOnChanges? Din v21, SimpleChanges e generic:
export class Legacy implements OnChanges {
readonly name = input('');
readonly age = input(0);
ngOnChanges(changes: SimpleChanges<Legacy>) {
if (changes.name) { // tipizat, fără changes['name']
console.log(changes.name.previousValue, '→', changes.name.currentValue);
}
}
}DestroyRef: curățenie reutilizabilă
Spre deosebire de ngOnDestroy (care există doar pe clase), DestroyRef poate fi injectat oriunde — inclusiv în funcții. Asta permite „hook-uri” reutilizabile care se curăță singure.
// DestroyRef funcționează oriunde există un context de injecție — ideal pentru funcții reutilizabile
export function useInterval(ms: number, fn: () => void): void {
const id = setInterval(fn, ms);
inject(DestroyRef).onDestroy(() => clearInterval(id));
}
export class Clock {
readonly now = signal(new Date());
constructor() {
useInterval(1000, () => this.now.set(new Date())); // se curăță singur
}
}afterNextRender / afterEveryRender
// afterNextRender — o dată; afterEveryRender — după fiecare randare a aplicației (rar nevoie)
afterNextRender({
read: () => this.el.nativeElement.offsetHeight, // citește layout-ul
// write: (height) => ... // apoi scrie (faze separate = fără thrashing)
});
// Rulează doar în browser (nu la SSR): locul potrivit pentru window, document, localStorage#1 A trecut build-ul?
#2 Da, și toate testele sunt verzi.
#3 Atunci deschid PR-ul.
#4 Dă-mi un semn când e făcut merge.
send() {
this.messages.update((list) => [...list, next]);
const el = this.chat().nativeElement;
el.scrollTop = el.scrollHeight; // DOM-ul are încă lista veche
}Curățenia pentru abonări, timere și listeneri e detaliată la Memory leaks: cum le găsești și le repari; codul care atinge window sau document trebuie să meargă și pe server: Cod compatibil cu serverul.
Când DA, când NU
- Cod existent care funcționează — nu e nevoie de rescriere forțată.
ngOnInitpentru inițializare care citește input-uri o singură dată (e încă valid și lizibil).- Interfețe cerute de librării (ex.
ControlValueAccessorclasic).
- Recalcularea unei valori când se schimbă un input →
computed(). - Resetarea stării locale la schimbarea unui input →
linkedSignal(). - Fetch la schimbarea unui input →
httpResource(). - Dezabonări în
ngOnDestroy→takeUntilDestroyed()/toSignal.
Capcane frecvente
- Input-uri în constructor: nu sunt setate. Un
input.requiredcitit acolo aruncă eroare. window/documentîn constructor cu SSR → eroare pe server. Mută codul înafterNextRender.ngOnChangesnu se declanșează la mutații interne ale obiectelor primite — doar la referințe noi.