Maîtrise des cycles de vie des composants Angular

Dans l'écosystème Angular, un composant traverse un cycle de vie bien défini, de sa création à sa detsruction. Chaque étape est marquée par des points d'ancrage, appelés "hooks", permettant aux développeurs d'intervenir précisément sur le comportement de l'application. Comprendre ces mécanismes est fondamental pour optimiser les performances et garantir la stabilité du code.

Tableau récapitulatif des hooks de cycle de vie

Hook Moment de déclenchement Fréquence Usage principal
ngOnChanges Après la modification d'une propriété @Input Multiple Réagir aux changements de données entrantes.
ngOnInit Une fois les propriétés d'entrée initialisées Unique Initialisation des données et appels API.
ngDoCheck Lors de chaque cycle de détection de changement Fréquent Détection personnalisée de modifications complexes.
ngAfterContentInit Après l'insertion du contenu externe (ng-content) Unique Interagir avec le contenu projeté.
ngAfterViewInit Après l'initialisation de la vue du composant et de ses enfants Unique Manipulation du DOM ou intégration de bibliothèques tierces.
ngOnDestroy Juste avant la suppression du composant Unique Nettoyage (désabonnements, timers).

Exploration détaillée et exemples pratiques

1. ngOnChanges : Surveillance des entrées

Ce hook reçoit un objet SimpleChanges contenant les valeurs actuelles et précédentes des propriétés décorées par @Input.

import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';

@Component({
  selector: 'app-fiche-article',
  standalone: true,
  template: `
    <div>
      <h2>{{ titre }}</h2>
      <p>Prix : {{ prix | currency:'EUR' }}</p>
    </div>
  `
})
export class FicheArticleComponent implements OnChanges {
  @Input() idReference!: string;
  @Input() titre: string = '';
  @Input() prix: number = 0;

  ngOnChanges(changes: SimpleChanges) {
    if (changes['idReference']) {
      const refActuelle = changes['idReference'].currentValue;
      const refPrecedente = changes['idReference'].previousValue;
      
      if (!changes['idReference'].firstChange && refActuelle !== refPrecedente) {
        this.actualiserDonnees(refActuelle);
      }
    }
  }

  private actualiserDonnees(id: string) {
    console.log(`Mise à jour pour la référence : ${id}`);
  }
}

2. ngOnInit : Le point de départ

C'est l'endroit idéal pour charger les données initiales. Contrairement au constructeur, ngOnInit garantit que les entrées (Inputs) sont disponibles.

import { Component, OnInit, Input } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-profil-utilisateur',
  standalone: true,
  template: `<p>Utilisateur : {{ profil?.nom }}</p>`
})
export class ProfilUtilisateurComponent implements OnInit {
  @Input() identifiant!: number;
  profil: any;

  constructor(private api: HttpClient) {}

  ngOnInit() {
    this.api.get(`https://api.example.com/users/${this.identifiant}`)
      .subscribe(res => this.profil = res);
  }
}

3. ngAfterViewInit : Accès au DOM

Ce hook intervient lorsque le template du composant est entièrement rendu. Il est souvent utilisé pour initialiser des graphiques ou des plugins nécessitant un élément HTML réel.

import { Component, AfterViewInit, ViewChild, ElementRef } from '@angular/core';

@Component({
  selector: 'app-zone-dessin',
  standalone: true,
  template: `<canvas #canevasPrincipal></canvas>`
})
export class ZoneDessinComponent implements AfterViewInit {
  @ViewChild('canevasPrincipal') canevasRef!: ElementRef<HTMLCanvasElement>;

  ngAfterViewInit() {
    const contexte = this.canevasRef.nativeElement.getContext('2d');
    if (contexte) {
      contexte.fillStyle = 'red';
      contexte.fillRect(10, 10, 100, 100);
    }
  }
}

4. ngOnDestroy : Prévention des fuites de mémoire

Essentiel pour libérer les ressources. Si vous utilisez des Observables ou des écouteurs d'événements globaux, vous devez les nettoyer ici.

import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription, timer } from 'rxjs';

@Component({
  selector: 'app-compteur-temps',
  standalone: true,
  template: `<p>Secondes écoulées : {{ secondes }}</p>`
})
export class CompteurTempsComponent implements OnInit, OnDestroy {
  secondes = 0;
  private fluxSoutenu?: Subscription;

  ngOnInit() {
    this.fluxSoutenu = timer(0, 1000).subscribe(val => this.secondes = val);
  }

  ngOnDestroy() {
    // Rupture de l'abonnement pour éviter que le timer continue en arrière-plan
    this.fluxSoutenu?.unsubscribe();
    console.log('Ressources libérées avec succès.');
  }
}

Ordre d'exécution chronologique

Lors de l'instanciation d'un composant, Angular suit cet ordre strict :

  1. ngOnChanges (si des entrées existent)
  2. ngOnInit
  3. ngDoCheck
  4. ngAfterContentInit
  5. ngAfterContentChecked
  6. ngAfterViewInit
  7. ngAfterViewChecked

Cycles de vie et Angular Signals

Avec l'introduction des Signals dans les versions récentes d'Angular, certains usages de ngDoCheck ou ngOnChanges peuvent être remplacés par la fonction effect() ou des signaux calculés (computed). Toutefois, les hooks traditionnels restent indispensables pour la gestion du cycle de vie du DOM et la destruction des ressources externes non réactives.

Étiquettes: Angular TypeScript Component-Lifecycle web-development frontend

Publié le 18 juin à 16h39