Angular Lifecycle Hooks: ngOnChanges, ngOnInit und mehr

Warum brauchen wir Lifecycle-Hooks?

Moderne Front-End-Frameworks verschieben die Anwendung von Staat zu Staat. Daten fördern diese Aktualisierungen. Diese Technologien interagieren mit den Daten, die wiederum den Zustand ändern. Bei jeder Zustandsänderung gibt es viele spezifische Momente, in denen bestimmte Vermögenswerte verfügbar werden.

In einem Fall ist die Vorlage möglicherweise fertig, in einem anderen Fall ist das Hochladen der Daten abgeschlossen. Die Codierung für jede Instanz erfordert ein Erkennungsmittel. Lifecycle-Hooks erfüllen dieses Bedürfnis. Moderne Front-End-Frameworks bieten eine Vielzahl von Lifecycle-Hooks. Winkel ist keine Ausnahme

Lifecycle Hooks erklärt

Lifecycle-Hooks sind zeitgesteuerte Methoden. Sie unterscheiden sich darin, wann und warum sie ausgeführt werden. Die Änderungserkennung löst diese Methoden aus. Sie werden abhängig von den Bedingungen des aktuellen Zyklus ausgeführt. Winkelläufe ändern die Erkennung ständig an ihren Daten. Lifecycle-Hooks helfen bei der Verwaltung der Auswirkungen.

Ein wichtiger Aspekt dieser Hooks ist ihre Ausführungsreihenfolge. Es weicht nie ab. Sie werden basierend auf einer vorhersagbaren Reihe von Lastereignissen ausgeführt, die aus einem Erkennungszyklus erzeugt werden. Dies macht sie vorhersehbar.

Einige Assets sind erst verfügbar, nachdem ein bestimmter Hook ausgeführt wurde. Natürlich wird ein Hook nur unter bestimmten Bedingungen ausgeführt, die im aktuellen Änderungserkennungszyklus festgelegt wurden.

Dieser Artikel beschreibt die Lifecycle-Hooks in der Reihenfolge ihrer Ausführung (wenn sie alle ausgeführt werden). Bestimmte Bedingungen verdienen die Aktivierung eines Hakens. Es gibt einige, die nach der Komponenteninitialisierung nur einmal ausgeführt werden.

Alle Lebenszyklusmethoden sind verfügbar von @angular/core. Obwohl nicht erforderlich, empfiehlt Angular, jeden Hook zu implementieren. Diese Vorgehensweise führt zu besseren Fehlermeldungen bezüglich der Komponente.

Reihenfolge der Ausführung von Lifecycle Hooks

ngOnChanges

ngOnChangesTrigger nach der Änderung @Inputgebundener Klassenmitglieder. Vom @Input()Dekorateur gebundene Daten stammen aus einer externen Quelle. Wenn die externe Quelle diese Daten auf erkennbare Weise ändert, durchläuft sie die @InputEigenschaft erneut.

Mit diesem Update wird ngOnChangessofort ausgelöst. Es wird auch bei der Initialisierung der Eingabedaten ausgelöst. Der Hook erhält einen optionalen Parameter vom Typ SimpleChanges. Dieser Wert enthält Informationen zu den geänderten eingabegebundenen Eigenschaften.

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); } }

Zusammenfassung: ParentComponent bindet Eingabedaten an die ChildComponent. Die Komponente empfängt diese Daten über ihre@InputEigenschaft. ngOnChangesFeuer. Nach fünf Sekunden wird dersetTimeoutRückruf ausgelöst. ParentComponent mutiert die Datenquelle der eingabegebundenen Eigenschaft von ChildComponent. Die neuen Daten fließen durch die Eingabeeigenschaft. ngOnChangesfeuert noch einmal.

ngOnInit

ngOnInitWird einmalig beim Initialisieren der input-bound ( @Input) - Eigenschaften einer Komponente ausgelöst. Das nächste Beispiel ähnelt dem letzten. Der Hook wird nicht ausgelöst, wenn ChildComponent die Eingabedaten empfängt. Vielmehr wird es direkt nach dem Rendern der Daten in die ChildComponent-Vorlage ausgelöst.

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); } }

Zusammenfassung: ParentComponent bindet Eingabedaten an die ChildComponent. ChildComponent empfängt diese Daten über seine@InputEigenschaft. Die Daten werden in die Vorlage gerendert. ngOnInitFeuer. Nach fünf Sekunden wird dersetTimeoutRückruf ausgelöst. ParentComponent mutiert die Datenquelle der eingabegebundenen Eigenschaft von ChildComponent. ngOnInit FEUERT NICHT .

ngOnInitist ein One-and-Do-Haken. Die Initialisierung ist das einzige Problem.

ngDoCheck

ngDoCheckwird bei jedem Änderungserkennungszyklus ausgelöst. Winkelläufe ändern häufig die Erkennung. Wenn Sie eine Aktion ausführen, wird der Zyklus ausgeführt. ngDoCheckBrände mit diesen Zyklen. Verwenden Sie es mit Vorsicht. Bei falscher Implementierung können Leistungsprobleme auftreten.

ngDoCheckMit dieser Option können Entwickler ihre Daten manuell überprüfen. Sie können bedingt ein neues Bewerbungsdatum auslösen. In Verbindung mit ChangeDetectorRefkönnen Entwickler ihre eigenen Prüfungen zur Änderungserkennung erstellen.

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(); } } }

Achten Sie auf die Konsole gegenüber dem Display. Die Daten werden vor dem Einfrieren bis zu "mittel" weitergeleitet. In diesem Zeitraum werden drei Änderungserkennungsrunden durchgeführt, wie in der Konsole angegeben. Eine weitere Runde der Änderungserkennung erfolgt, wenn 'final' an das Ende von verschoben wird this.data. Dann erfolgt eine letzte Runde der Änderungserkennung. Die Auswertung der if-Anweisung stellt fest, dass keine Aktualisierungen der Ansicht erforderlich sind.

Zusammenfassung: Die Klasse wird nach zwei Runden der Änderungserkennung instanziiert. Der Klassenkonstruktor wirdsetTimeoutzweimalinitiiert. Nach drei SekundensetTimeoutlöstder erstedie Änderungserkennung aus. ngDoCheckmarkiert die Anzeige für ein Update. Drei Sekunden spätersetTimeoutlöstder zweitedie Änderungserkennung aus. Laut Auswertung von sind keine Ansichtsaktualisierungen erforderlichngDoCheck.

Warnung

Bevor Sie fortfahren, lernen Sie den Unterschied zwischen dem Inhalts-DOM und dem Ansichts-DOM kennen (DOM steht für Document Object Model).

Das Inhalts-DOM definiert das innerHTML von Direktivenelementen. Umgekehrt ist das Ansichts-DOM die Vorlage einer Komponente, ausgenommen alle in einer Direktive verschachtelten HTML-Vorlagen. Weitere Informationen finden Sie in diesem Blogbeitrag.

ngAfterContentInit

ngAfterContentInitwird ausgelöst, nachdem das Inhalts-DOM der Komponente initialisiert wurde (wird zum ersten Mal geladen). Das Warten auf @ContentChild(ren)Abfragen ist der Hauptanwendungsfall des Hooks.

@ContentChild(ren)Abfragen ergeben Elementreferenzen für das Inhalts-DOM. Daher sind sie erst verfügbar, nachdem das Inhalts-DOM geladen wurde. Daher werden warum ngAfterContentInitund sein Gegenstück ngAfterContentCheckedverwendet.

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 { }

Die @ContentChildAbfrageergebnisse sind verfügbar von ngAfterContentInit. Renderer2aktualisiert das Inhalts-DOM von BComponent, das ein h3Tag und eine CComponent enthält. Dies ist ein häufiges Beispiel für die Inhaltsprojektion.

Zusammenfassung: Das Rendern beginnt mit AComponent. Damit dies abgeschlossen ist, muss AComponent BComponent rendern. BComponent projiziert Inhalte, die in seinem Element verschachtelt sind, über dasElement. CComponent ist Teil des projizierten Inhalts. Der projizierte Inhalt beendet das Rendern. ngAfterContentInitFeuer. BComponent beendet das Rendern. AComponent beendet das Rendern. ngAfterContentInitwird nicht wieder feuern.

ngAfterContentChecked

ngAfterContentCheckedwird nach jedem Zyklus der Änderungserkennung ausgelöst, der auf das Inhalts-DOM abzielt. Auf diese Weise können Entwickler vereinfachen, wie das Inhalts-DOM auf die Erkennung von Änderungen reagiert. ngAfterContentCheckedkann häufig ausgelöst werden und bei schlechter Implementierung Leistungsprobleme verursachen.

ngAfterContentCheckedwird auch während der Initialisierungsphasen einer Komponente ausgelöst. Es kommt gleich danach 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