Crochets angulaires du cycle de vie: ngOnChanges, ngOnInit, etc.

Pourquoi avons-nous besoin de crochets de cycle de vie?

Les frameworks frontaux modernes déplacent l'application d'un état à l'autre. Les données alimentent ces mises à jour. Ces technologies interagissent avec les données qui à leur tour transforment l'état. À chaque changement d'état, il y a de nombreux moments spécifiques où certains actifs deviennent disponibles.

Dans un cas, le modèle peut être prêt, dans une autre, le téléchargement des données sera terminé. Le codage de chaque instance nécessite un moyen de détection. Les hooks de cycle de vie répondent à ce besoin. Les frameworks frontaux modernes s'emballent avec une variété de hooks de cycle de vie. Angular ne fait pas exception

Explication des crochets du cycle de vie

Les hooks de cycle de vie sont des méthodes chronométrées. Ils diffèrent quant au moment et à la raison de l'exécution. La détection des changements déclenche ces méthodes. Ils s'exécutent en fonction des conditions du cycle en cours. Angular exécute la détection de changement en permanence sur ses données. Les hooks de cycle de vie aident à gérer ses effets.

Un aspect important de ces crochets est leur ordre d'exécution. Il ne dévie jamais. Ils s'exécutent sur la base d'une série prévisible d'événements de charge produits à partir d'un cycle de détection. Cela les rend prévisibles.

Certains actifs ne sont disponibles qu'après l'exécution d'un certain hook. Bien entendu, un hook ne s'exécute que dans certaines conditions définies dans le cycle de détection de changement actuel.

Cet article présente les hooks de cycle de vie dans l'ordre de leur exécution (s'ils s'exécutent tous). Certaines conditions méritent l'activation d'un crochet. Il y en a quelques-uns qui ne s'exécutent qu'une seule fois après l'initialisation du composant.

Toutes les méthodes de cycle de vie sont disponibles à partir de @angular/core. Bien que cela ne soit pas obligatoire, Angular recommande d'implémenter chaque hook. Cette pratique conduit à de meilleurs messages d'erreur concernant le composant.

Ordre d'exécution des hooks du cycle de vie

ngOnChanges

ngOnChangesdéclencheurs suite à la modification des @Inputmembres de classe liés. Les données liées par le @Input()décorateur proviennent d'une source externe. Lorsque la source externe modifie ces données de manière détectable, elle passe à @Inputnouveau par la propriété.

Avec cette mise à jour, ngOnChangesse déclenche immédiatement. Il se déclenche également lors de l'initialisation des données d'entrée. Le hook reçoit un paramètre facultatif de type SimpleChanges. Cette valeur contient des informations sur les propriétés liées à l'entrée modifiées.

import { Component, Input, OnChanges } from '@angular/core'; @Component({ selector: 'app-child', template: ` 

Child Component

TICKS: {{ lifecycleTicks }}

DATA: {{ data }}

` }) export class ChildComponent implements OnChanges { @Input() data: string; lifecycleTicks: number = 0; ngOnChanges() { this.lifecycleTicks++; } } @Component({ selector: 'app-parent', template: `

ngOnChanges Example

` }) export class ParentComponent { arbitraryData: string = 'initial'; constructor() { setTimeout(() => { this.arbitraryData = 'final'; }, 5000); } }

Résumé: ParentComponent lie les données d'entrée au ChildComponent. Le composant reçoit ces données via sa@Inputpropriété. ngOnChangesles feux. Après cinq secondes, lesetTimeoutrappel se déclenche. ParentComponent mute la source de données de la propriété liée à l'entrée de ChildComponent. Les nouvelles données transitent par la propriété input. ngOnChangesincendies encore une fois.

ngOnInit

ngOnInitse déclenche une fois lors de l'initialisation des @Inputpropriétés input-bound ( ) d'un composant . Le prochain exemple ressemblera au dernier. Le hook ne se déclenche pas lorsque ChildComponent reçoit les données d'entrée. Au lieu de cela, il se déclenche juste après le rendu des données dans le modèle ChildComponent.

import { Component, Input, OnInit } from '@angular/core'; @Component({ selector: 'app-child', template: ` 

Child Component

TICKS: {{ lifecycleTicks }}

DATA: {{ data }}

` }) export class ChildComponent implements OnInit { @Input() data: string; lifecycleTicks: number = 0; ngOnInit() { this.lifecycleTicks++; } } @Component({ selector: 'app-parent', template: `

ngOnInit Example

` }) export class ParentComponent { arbitraryData: string = 'initial'; constructor() { setTimeout(() => { this.arbitraryData = 'final'; }, 5000); } }

Résumé: ParentComponent lie les données d'entrée au ChildComponent. ChildComponent reçoit ces données via sa@Inputpropriété. Les données sont rendues au modèle. ngOnInitles feux. Après cinq secondes, lesetTimeoutrappel se déclenche. ParentComponent mute la source de données de la propriété liée à l'entrée de ChildComponent. ngOnInit NE SE FEU .

ngOnInitest un crochet unique. L'initialisation est sa seule préoccupation.

ngDoCheck

ngDoCheckse déclenche à chaque cycle de détection de changement. Les exécutions angulaires changent fréquemment de détection. Toute action entraînera un cycle. ngDoCheckincendies avec ces cycles. Utilisez-le avec prudence. Il peut créer des problèmes de performances lorsqu'il est mis en œuvre de manière incorrecte.

ngDoCheckpermet aux développeurs de vérifier leurs données manuellement. Ils peuvent déclencher une nouvelle date de candidature sous condition. En collaboration avec ChangeDetectorRef, les développeurs peuvent créer leurs propres contrôles pour la détection des modifications.

import { Component, DoCheck, ChangeDetectorRef } from '@angular/core'; @Component({ selector: 'app-example', template: ` 

ngDoCheck Example

DATA: {{ data[data.length - 1] }}

` }) export class ExampleComponent implements DoCheck { lifecycleTicks: number = 0; oldTheData: string; data: string[] = ['initial']; constructor(private changeDetector: ChangeDetectorRef) { this.changeDetector.detach(); // lets the class perform its own change detection setTimeout(() => { this.oldTheData = 'final'; // intentional error this.data.push('intermediate'); }, 3000); setTimeout(() => { this.data.push('final'); this.changeDetector.markForCheck(); }, 6000); } ngDoCheck() { console.log(++this.lifecycleTicks); if (this.data[this.data.length - 1] !== this.oldTheData) { this.changeDetector.detectChanges(); } } }

Faites attention à la console par rapport à l'écran. Les données progressent jusqu'à «intermédiaire» avant le gel. Trois cycles de détection de changement se produisent pendant cette période, comme indiqué dans la console. Un autre cycle de détection de changement se produit lorsque la «finale» est poussée à la fin de this.data. Un dernier cycle de détection de changement se produit alors. L'évaluation de l'instruction if détermine qu'aucune mise à jour de la vue n'est nécessaire.

Résumé: la classe s'instancie après deux cycles de détection de changement. Le constructeur de classe démarresetTimeoutdeux fois. Au bout de trois secondes, le premiersetTimeoutdéclenche la détection de changement. ngDoCheckmarque l'affichage pour une mise à jour. Trois secondes plus tard, le secondsetTimeoutdéclenche la détection de changement. Aucune mise à jour de vue n'est nécessaire selon l'évaluation dengDoCheck.

Attention

Avant de continuer, découvrez la différence entre le contenu DOM et la vue DOM (DOM signifie Document Object Model).

Le contenu DOM définit le innerHTML des éléments de directive. A l'inverse, la vue DOM est un modèle de composant excluant tout modèle HTML imbriqué dans une directive. Pour une meilleure compréhension, reportez-vous à cet article de blog.

ngAfterContentInit

ngAfterContentInitse déclenche après l'initialisation du contenu DOM du composant (se charge pour la première fois). L'attente de @ContentChild(ren)requêtes est le principal cas d'utilisation du hook.

@ContentChild(ren)les requêtes génèrent des références d'élément pour le contenu DOM. En tant que tels, ils ne sont disponibles qu'après le chargement du contenu DOM. D'où pourquoi ngAfterContentInitet son équivalent ngAfterContentCheckedsont utilisés.

import { Component, ContentChild, AfterContentInit, ElementRef, Renderer2 } from '@angular/core'; @Component({ selector: 'app-c', template: ` 

I am C.

Hello World!

` }) export class CComponent { } @Component({ selector: 'app-b', template: `

I am B.

` }) export class BComponent implements AfterContentInit { @ContentChild("BHeader", { read: ElementRef }) hRef: ElementRef; @ContentChild(CComponent, { read: ElementRef }) cRef: ElementRef; constructor(private renderer: Renderer2) { } ngAfterContentInit() { this.renderer.setStyle(this.hRef.nativeElement, 'background-color', 'yellow') this.renderer.setStyle(this.cRef.nativeElement.children.item(0), 'background-color', 'pink'); this.renderer.setStyle(this.cRef.nativeElement.children.item(1), 'background-color', 'red'); } } @Component({ selector: 'app-a', template: `

ngAfterContentInit Example

I am A.

BComponent Content DOM

` }) export class AComponent { }

Les @ContentChildrésultats de la requête sont disponibles à partir de ngAfterContentInit. Renderer2met à jour le contenu DOM de BComponent contenant une h3balise et CComponent. Ceci est un exemple courant de projection de contenu.

Résumé: le rendu commence par AComponent. Pour qu'il se termine, AComponent doit rendre BComponent. BComponent projette le contenu imbriqué dans son élément via l'élément. CComponent fait partie du contenu projeté. Le contenu projeté termine le rendu. ngAfterContentInitles feux. BComponent termine le rendu. Un composant termine le rendu. ngAfterContentInitne tirera plus.

ngAfterContentChecked

ngAfterContentCheckedse déclenche après chaque cycle de détection de changement ciblant le DOM de contenu. Cela permet aux développeurs de faciliter la façon dont le contenu DOM réagit à la détection des changements. ngAfterContentCheckedpeut se déclencher fréquemment et entraîner des problèmes de performances s'il est mal implémenté.

ngAfterContentCheckedse déclenche également pendant les étapes d'initialisation d'un composant. Cela vient juste après ngAfterContentInit.

import { Component, ContentChild, AfterContentChecked, ElementRef, Renderer2 } from '@angular/core'; @Component({ selector: 'app-c', template: ` 

I am C.

Hello World!

` }) export class CComponent { } @Component({ selector: 'app-b', template: `

I am B.

CLICK ` }) export class BComponent implements AfterContentChecked { @ContentChild("BHeader", { read: ElementRef }) hRef: ElementRef; @ContentChild(CComponent, { read: ElementRef }) cRef: ElementRef; constructor(private renderer: Renderer2) { } randomRGB(): string { return `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)})`; } ngAfterContentChecked() { this.renderer.setStyle(this.hRef.nativeElement, 'background-color', this.randomRGB()); this.renderer.setStyle(this.cRef.nativeElement.children.item(0), 'background-color', this.randomRGB()); this.renderer.setStyle(this.cRef.nativeElement.children.item(1), 'background-color', this.randomRGB()); } } @Component({ selector: 'app-a', template: `

ngAfterContentChecked Example

I am A.

BComponent Content DOM

` }) export class AComponent { }

This hardly differs from ngAfterContentInit. A mere was added to BComponent. Clicking it causes a change detection loop. This activates the hook as indicated by the randomization of background-color.

Summary: Rendering starts with AComponent. For it to finish, AComponent must render BComponent. BComponent projects content nested in its element through the element. CComponent is part of the projected content. The projected content finishes rendering. ngAfterContentChecked fires. BComponent finishes rendering. AComponent finishes rendering. ngAfterContentChecked may fire again through change detection.

ngAfterViewInit

ngAfterViewInit fires once after the view DOM finishes initializing. The view always loads right after the content. ngAfterViewInit waits on @ViewChild(ren) queries to resolve. These elements are queried from within the same view of the component.

In the example below, BComponent’s h3 header is queried. ngAfterViewInit executes as soon as the query’s results are available.

import { Component, ViewChild, AfterViewInit, ElementRef, Renderer2 } from '@angular/core'; @Component({ selector: 'app-c', template: ` 

I am C.

Hello World!

` }) export class CComponent { } @Component({ selector: 'app-b', template: `

I am B.

` }) export class BComponent implements AfterViewInit { @ViewChild("BStatement", { read: ElementRef }) pStmt: ElementRef; constructor(private renderer: Renderer2) { } ngAfterViewInit() { this.renderer.setStyle(this.pStmt.nativeElement, 'background-color', 'yellow'); } } @Component({ selector: 'app-a', template: `

ngAfterViewInit Example

I am A.

BComponent Content DOM

` }) export class AComponent { }

Renderer2 changes the background color of BComponent’s header. This indicates the view element was successfully queried thanks to ngAfterViewInit.

Summary: Rendering starts with AComponent. For it to finish, AComponent must render BComponent. BComponent projects content nested in its element through the element. CComponent is part of the projected content. The projected content finishes rendering. BComponent finishes rendering. ngAfterViewInit fires. AComponent finishes rendering. ngAfterViewInit will not fire again.

ngAfterViewChecked

ngAfterViewChecked fires after any change detection cycle targeting the component’s view. The ngAfterViewChecked hook lets developers facilitate how change detection affects the view DOM.

import { Component, ViewChild, AfterViewChecked, ElementRef, Renderer2 } from '@angular/core'; @Component({ selector: 'app-c', template: ` 

I am C.

Hello World!

` }) export class CComponent { } @Component({ selector: 'app-b', template: `

I am B.

CLICK ` }) export class BComponent implements AfterViewChecked { @ViewChild("BStatement", { read: ElementRef }) pStmt: ElementRef; constructor(private renderer: Renderer2) { } randomRGB(): string { return `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)})`; } ngAfterViewChecked() { this.renderer.setStyle(this.pStmt.nativeElement, 'background-color', this.randomRGB()); } } @Component({ selector: 'app-a', template: `

ngAfterViewChecked Example

I am A.

BComponent Content DOM

` }) export class AComponent { }

Summary: Rendering starts with AComponent. For it to finish, AComponent must render BComponent. BComponent projects content nested in its element through the element. CComponent is part of the projected content. The projected content finishes rendering. BComponent finishes rendering. ngAfterViewChecked fires. AComponent finishes rendering. ngAfterViewChecked may fire again through change detection.

Clicking the element initiates a round of change detection. ngAfterContentChecked fires and randomizes the background-color of the queried elements each button click.

ngOnDestroy

ngOnDestroy fires upon a component’s removal from the view and subsequent DOM. This hook provides a chance to clean up any loose ends before a component’s deletion.

import { Directive, Component, OnDestroy } from '@angular/core'; @Directive({ selector: '[appDestroyListener]' }) export class DestroyListenerDirective implements OnDestroy { ngOnDestroy() { console.log("Goodbye World!"); } } @Component({ selector: 'app-example', template: ` 

ngOnDestroy Example

TOGGLE DESTROY

I can be destroyed!

` }) export class ExampleComponent { destroy: boolean = true; toggleDestroy() { this.destroy = !this.destroy; } }

Summary: The button is clicked. ExampleComponent’s destroy member toggles false. The structural directive *ngIf evaluates to false. ngOnDestroy fires. *ngIf removes its host . This process repeats any number of times clicking the button to toggle destroy to false.

Conclusion

Remember that certain conditions must be met for each hook. They will always execute in order of each other regardless. This makes hooks predictable enough to work with even if some do not execute.

With lifecycle hooks, timing the execution of a class is easy. They let developers track where change detection is occurring and how the application should react. They stall for code that requires load-based dependencies available only after sometime.

The component lifecycle characterizes modern front end frameworks. Angular lays out its lifecycle by providing the aforementioned hooks.

Sources

  • Angular Team. “Lifecycle Hooks”. Google. Accessed 2 June 2018
  • Gechev, Minko. “ViewChildren and ContentChildren in Angular”. Accessed 2 June 2018

Resources

  • Angular Documentation
  • Angular GitHub Repository
  • Lifecycle Hooks in Depth