Loupe

Le cycle de vie d'un composant Angular

Lors du développement d'une application Angular, il est important de comprendre le fonctionnement des composants et notamment leur cycle de vie. Ce cycle de vie est géré par Angular, c'est donc Angular qui va créer le composant, faire son rendu et enfin le détruire lorsque c'est nécessaire.

Le développeur peut étendre ce cycle de vie en se branchant sur les lifecycle hooks mis à disposition par la team Angular dans @angular/core.

Les lifecycle hooks disponibles

Les lifecycle hooks sont utilisables sur les composants, mais aussi sur les directives, à l'exception de certains hooks qui sont exclusifs aux composants.

(Le constructeur)

Petite parenthèse ici pour le constructeur, qui n'est bien évidemment pas un hook, mais qui fait tout de même partie du cycle de vie du composant : sa création. Il est logiquement appelé en premier, et c'est à ce moment que les dépendances (services) sont injectées dans le composant par Angular.

Au moment de la construction du composant, les inputs passés aux composants ne sont pas encore renseignés. Le constructeur a pour principal objectif la création du composant, sans complexité, qui n'a ni besoin d'être asynchrone, ni besoin des inputs. 

Pour toute initialisation plus complexe, ou qui nécessite les inputs et/ou de l'asynchronisme, on s'orientera vers ngOnInit.

ngOnInit

L'initialisation du composant se fait alors dans ce lifecycle hook. Les inputs passés au composant sont alors disponibles. Il est appelé tout de suite après le constructeur, et c'est une bonne nouvelle pour l'expérience utilisateur.

Par exemple, si un composant, pour s'initialiser, doit faire un appel à une API pour récupérer des données, disons des produits, c'est dans le ngOnInit que nous devons le faire.

export class LifecycleComponent implements OnInit {
  products: Product[] = [];

  constructor(private fakeService: FakeService) { }

  ngOnInit() {
    this.fakeService.getProducts().subscribe(products => {
      this.products = products;
    });
  }
}

Dans l'exemple ci-dessus, nous récupérons un FakeService par injection de dépendances, dans le constructeur. Puis dans la méthode ngOnInit, nous récupérons de manière asynchrone la liste des produits pour mettre à jour le composant.

ngOnChanges

Il est appelé lorsqu'une propriété input est définie ou bien modifiée. Un objet est passé en paramètre et possède des informations sur le changement de la propriété. Par exemple, avec un input nommé prop1 de type number, qui passe de 1 à 2, nous aurions l'objet suivant :

// implémentation de l'interface OnChanges
ngOnChanges(changes: SimpleChanges) : void {
    // logique du composant
}

// exemple de valeur
{
   "prop1":{
      "currentValue":2,
      "firstChange":false,
      "previousValue":1
   }
}

// interface du SimpleChanges
export interface SimpleChanges {
    [propName: string]: SimpleChanges;
}

Il est important de savoir que ngOnChanges est levé uniquement lorsque c'est l'appelant du composant qui modifie les inputs. Si le composant lui-même modifie son input, la méthode n'est pas levée.

Prenons l'exemple suivant :

export class LifecycleComponent implements OnChanges {
  @Input() label: string;

  ngOnChanges(changes: SimpleChanges): void {
    const labelChange = changes['label'];
    if (labelChange.firstChange) {
      console.log(`[label] La propriété vient d'être initialisée à ${labelChange.currentValue}`);
    } else {
      console.log(`[label] ${labelChange.previousValue} -> ${labelChange.currentValue}`);
    }
  }
}

Dans cet exemple, si un composant externe passe un input au composant LifecycleComponent, et qu'il modifie cet input par la suite, nous aurions les logs suivants :

16:56:19.500 [label] La propriété vient d'être initialisée à Premier texte
16:56:19.661 [label] Premier texte -> Second texte



ngDoCheck

Le hook ngDoCheck permet d'étendre la détection de changements. Et donc, pour savoir quand cette fonction est appelée, il est essentiel de comprendre la détection de changements en elle-même, ce qui est un sujet à part qui sera traité dans un article futur (le lien sera ajouté ici même).

Par défaut, la détection de changement est levée très souvent, il est alors indispensable de minimiser le coût en performance de ce qu'elle fait, sous peine de voir un réel impact sur les performances de l'application !

ngAfterContentInit

Le lifecycle hook ngAfterContentInit est appelé tout juste après le premier ngDoCheck, qui lui-même a suivi ngOnInit.

Concrètement, ce hook est levé une fois que le contenu externe est bien projeté dans le composant. On parle ici de transclusion.

Pour résumer rapidement, il est possible de passer du contenu HTML et donc un composant, à un composant qui possède une balise ng-content

Prenons l'exemple d'un composant PopupComponent dans lequel il est possible de projeter du contenu :

@Component({
    selector: 'popup',
    template: '<div class="popup"><ng-content></ng-content></div>'
})
export class PopupComponent {
}

Ainsi, le contenu sera injecté dans le composant à la place de la balise ng-content. Nous pouvons alors utiliser le composant dans l'application.

<popup><div>Afficher ce <span class="une-classe">contenu</span> dans le composant popup !</div></popup>

Pour en revenir au hook ngAfterContentInit, c'est donc lui qui nous permet d'exécuter du code lorsque le contenu est bien transcludé dans la balise ng-content.

Par exemple, en combinaison avec le décorateur @ContentChild, il est possible de récupérer l'instance d'un composant, ou la référence vers l'élement transcludé :

export class PopupComponent {
@ContentChild(SubComponent) subComponent: SubComponent;

    ngAfterContentInit() {
        this.subComponent.myMethod();
        console.log(this.subComponent.myProperty);
    }
}

 Dans l'exemple ci-dessus, on récupère l'instance d'un composant SubComponent. On peut alors accéder à ses méthodes, ses propriétés, etc...

La transclusion est un sujet qui mérite son propre article, le lien sera aussi ajouté ici même lorsqu'il sera rédigé.

 ngAfterContentChecked

Dans la même idée que le hook précédent, le hook ngAfterContentChecked est appelé cette fois-ci après chaque vérification du contenu projeté. La méthode est appelée après ngAfterContentInit, puis après chaque ngDoCheck.

ngAfterViewInit

Une fois que la vue du composant et celle de tous ces composants enfants (inclus dans le template du composant) sont chargées, le lifecycle hook ngAfterViewInit est appelé. Cela arrive après le premier ngAfterContentChecked.

Très similaire au ngAfterContentInit, Le ngAfterViewInit permet de récupérer les instances et références vers les éléments HTML aussi, mais cette fois-ci uniquement ceux qui sont présents dans le template du composant lui-même. Il y a donc une séparation entre le contenu direct du composant et celui qu'on lui projette via transclusion.

Petit changement : c'est le décorateur @ViewChild qu'il faut utiliser !

export class ParentComponent{
@ViewChild(ChildComponent) childComponent: ChildComponent;
    ngAfterViewInit() {
       this.childComponent.myMethod();
        console.log(this.childComponent.myProperty);
    }
}

ngAfterViewChecked

Ce lifecycle hook est exécuté une fois que les bindings du composant et celui de ses composants enfants ont été vérifiés, et cela même si rien n'a changé. De manière générale, ngAfterViewChecked permet de gérer les cas où le composant parent attend une modification du composant enfant.

Il est appelé après ngAfterViewInit, puis après chaque ngAfterContentChecked.

ngOnDestroy

Comme son nom l'indique, ngOnDestroy permet de s'attacher à la destruction du composant. Il est exécuté juste avant la destruction d'un composant et permet alors de réaliser le nettoyage adéquat de son composant. C'est ici qu'on veut se désabonner des Observables ainsi que des events handlers sur lesquels le composant s'est abonné.

Par exemple, supposons qu'un composant soit abonné à une Observable.timer. Il faut se désabonner soit même lors de la destruction du composant. Cela se fait en récupérant la Subscription renvoyée par la méthode subscribe(), puis en exécutant la méthode unsubscribe() dessus.

timerSubscription: Subscription;

ngOnDestroy() {
    this.timerSubscription.unsubscribe();
}

En résumé 

  • ngOnChanges : il est appelé lorsqu'un input est défini ou modifié de l'extérieur. L'état des modifications sur les inputs est fourni en paramètre
  • ngOnInit : il est appelé une seule fois et permet de réaliser l'initialisation du composant, qu'elle soit lourde ou asynchrone (on ne touche pas au constructeur pour ça)
  • ngDoCheck : il est appelé après chaque détection de changements
  • ngAfterContentInit : il est appelé une fois que le contenu externe est projeté dans le composant (transclusion)
  • ngAfterContentChecked : il est appelé chaque fois qu'une vérification du contenu externe (transclusion) est faite
  • ngAfterViewInit : il est appelé dès lors que la vue du composant ainsi que celle de ses enfants sont initialisés
  • ngAfterViewChecked : il est appelé après chaque vérification des vues du composant et des vues des composants enfants.
  • ngOnDestroy : il est appelé juste avant que le composant soit détruit par Angular.

A noter, les méthodes ngAfterContentInit, ngAfterContentChecked, ngAfterViewInit et ngAfterViewChecked sont exclusives aux composants, tandis que toutes les autres le sont aussi pour les directives.

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus