Maîtrisez tous les lifecycle hooks d'Angular : ngOnInit, ngOnDestroy, ngOnChanges, ngAfterViewInit et les nouveaux hooks de rendu. Guide complet avec exemples pratiques.
Vue d'ensemble des lifecycle hooks
Les lifecycle hooks sont des méthodes spéciales appelées automatiquement par Angular à des moments précis du cycle de vie d'un composant ou d'une directive. Ils permettent d'exécuter du code à l'initialisation, lors des changements, ou à la destruction du composant.
implements OnInit offre une vérification de type au compile.
Liste complète des hooks :
- ngOnChanges — déclenché quand un
@Input()change (avant ngOnInit) - ngOnInit — une seule fois après le premier ngOnChanges
- ngDoCheck — à chaque cycle de détection de changement
- ngAfterContentInit — après l'insertion du contenu projeté (
ng-content) - ngAfterContentChecked — après chaque vérification du contenu projeté
- ngAfterViewInit — après l'initialisation de la vue et des vues enfants
- ngAfterViewChecked — après chaque vérification de la vue
- ngOnDestroy — juste avant la destruction du composant
ngOnInit — initialisation du composant
ngOnInit est le hook le plus utilisé. Il s'exécute une seule fois après que les propriétés @Input() ont été initialisées. C'est ici qu'on fait les appels API, les abonnements, et l'initialisation de l'état.
import { Component, OnInit } from '@angular/core';
import { UserService } from './user.service';
@Component({ selector: 'app-profile', template: '...' })
export class ProfileComponent implements OnInit {
user: User | null = null;
constructor(private userService: UserService) {}
ngOnInit(): void {
// Appel API après que les @Input() sont disponibles
this.userService.getCurrentUser().subscribe(user => {
this.user = user;
});
}
}
ngOnInit est le bon endroit pour la logique d'initialisation — les @Input() ne sont pas encore disponibles dans le constructeur.
ngOnChanges — réaction aux inputs
ngOnChanges est appelé avant ngOnInit et à chaque fois qu'un @Input() change. Il reçoit un objet SimpleChanges contenant les valeurs précédentes et actuelles.
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
@Component({ selector: 'app-chart', template: '...' })
export class ChartComponent implements OnChanges {
@Input() data: number[] = [];
@Input() title = '';
ngOnChanges(changes: SimpleChanges): void {
if (changes['data']) {
const prev = changes['data'].previousValue;
const curr = changes['data'].currentValue;
const isFirst = changes['data'].firstChange;
console.info(`data changé : ${prev} → ${curr} (premier: ${isFirst})`);
this.redrawChart();
}
if (changes['title'] && !changes['title'].firstChange) {
// Réagir uniquement aux changements suivants (pas l'init)
this.updateTitle(changes['title'].currentValue);
}
}
private redrawChart(): void { /* ... */ }
private updateTitle(t: string): void { /* ... */ }
}
ngOnChanges n'est déclenché que pour les @Input() primitifs ou les changements de référence d'objet. Muter un objet existant ne déclenchera pas ngOnChanges.
ngOnDestroy — nettoyage des ressources
ngOnDestroy est appelé juste avant qu'Angular détruise le composant. C'est impératif de l'utiliser pour éviter les memory leaks : désabonnements, suppression de timers, fermeture de WebSockets.
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subject, interval } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Component({ selector: 'app-timer', template: '{{ count }}' })
export class TimerComponent implements OnInit, OnDestroy {
count = 0;
private destroy$ = new Subject<void>();
ngOnInit(): void {
// Le Subject coupe automatiquement l'abonnement à la destruction
interval(1000).pipe(
takeUntil(this.destroy$)
).subscribe(() => this.count++);
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
}
takeUntilDestroyed() depuis @angular/core/rxjs-interop pour éviter le boilerplate du Subject.
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
export class TimerComponent {
count = 0;
constructor() {
interval(1000).pipe(
takeUntilDestroyed() // se détruit automatiquement avec le composant
).subscribe(() => this.count++);
}
}
ngAfterViewInit et ngAfterContentInit
Ces hooks donnent accès aux éléments du DOM et aux composants enfants après leur initialisation.
ngAfterViewInit — accès aux @ViewChild et éléments du template :
import { Component, AfterViewInit, ViewChild, ElementRef } from '@angular/core';
@Component({
selector: 'app-canvas',
template: '<canvas #myCanvas></canvas>'
})
export class CanvasComponent implements AfterViewInit {
@ViewChild('myCanvas') canvas!: ElementRef<HTMLCanvasElement>;
ngAfterViewInit(): void {
// Le canvas est disponible ici — pas avant
const ctx = this.canvas.nativeElement.getContext('2d');
ctx?.fillRect(0, 0, 100, 100);
}
}
ngAfterContentInit — accès au contenu projeté via @ContentChild :
import { Component, AfterContentInit, ContentChild } from '@angular/core';
@Component({
selector: 'app-card',
template: '<ng-content></ng-content>'
})
export class CardComponent implements AfterContentInit {
@ContentChild('cardTitle') title!: ElementRef;
ngAfterContentInit(): void {
// Le contenu projeté est disponible ici
console.info('Titre projeté :', this.title?.nativeElement.textContent);
}
}
ngAfterViewChecked ou ngAfterContentChecked — cela provoquerait une erreur "Expression changed after check" en mode développement.
ngDoCheck — détection personnalisée
ngDoCheck est déclenché à chaque cycle de détection de changement, même si aucun @Input() n'a changé. Il permet de détecter des mutations que ngOnChanges ne voit pas (mutation d'un tableau ou d'un objet).
import { Component, DoCheck, Input, IterableDiffer, IterableDiffers } from '@angular/core';
@Component({ selector: 'app-list', template: '...' })
export class ListComponent implements DoCheck {
@Input() items: string[] = [];
private differ: IterableDiffer<string>;
constructor(differs: IterableDiffers) {
this.differ = differs.find([]).create();
}
ngDoCheck(): void {
const changes = this.differ.diff(this.items);
if (changes) {
console.info('Tableau muté :');
changes.forEachAddedItem(r => console.info('Ajout :', r.item));
changes.forEachRemovedItem(r => console.info('Suppression :', r.item));
}
}
}
ngDoCheck est appelé très fréquemment. Gardez son implémentation aussi légère que possible, ou préférez un observable/signal pour réagir aux changements.
afterNextRender et afterRender (Angular 16+)
Angular 16 a introduit deux nouvelles fonctions de hook de rendu, distinctes du système de lifecycle classique. Elles s'exécutent dans le contexte du navigateur uniquement (pas en SSR).
afterNextRender() — s'exécute une seule fois après le prochain rendu :
import { Component, afterNextRender, ElementRef, viewChild } from '@angular/core';
@Component({ selector: 'app-chart', template: '<canvas #chart></canvas>' })
export class ChartComponent {
private chart = viewChild<ElementRef>('chart');
constructor() {
afterNextRender(() => {
// Initialisation d'une librairie JS tierce (Chart.js, D3...)
// Exécuté une seule fois, après le premier rendu
initChart(this.chart()?.nativeElement);
});
}
}
afterRender() — s'exécute après chaque rendu :
import { afterRender } from '@angular/core';
export class ScrollComponent {
constructor() {
afterRender(() => {
// Mis à jour à chaque rendu — utile pour mesures DOM
this.updateScrollPosition();
});
}
}
afterNextRender() à ngAfterViewInit pour l'intégration de librairies DOM tierces dans les applications standalone. Ces hooks sont compatibles SSR et ne s'exécutent pas côté serveur.
Ordre d'exécution complet
Voici l'ordre exact dans lequel Angular appelle les hooks sur un composant :
// Ordre de création
1. constructor()
2. ngOnChanges() ← si @Input() présents
3. ngOnInit()
4. ngDoCheck()
5. ngAfterContentInit()
6. ngAfterContentChecked()
7. ngAfterViewInit()
8. ngAfterViewChecked()
// Cycles suivants (à chaque détection de changement)
ngOnChanges() ← si un @Input() a changé
ngDoCheck()
ngAfterContentChecked()
ngAfterViewChecked()
// Destruction
ngOnDestroy()
ngOnInit pour l'initialisation, ngOnChanges pour réagir aux inputs, et ngOnDestroy pour le nettoyage. Les autres hooks restent utiles dans des cas spécifiques.