Angular Lifecycle Hooks: ngOnChanges, ngOnInit och mer

Varför behöver vi livscykelkrokar?

Moderna frontramar flyttar applikationen från stat till stat. Data driver dessa uppdateringar. Dessa tekniker interagerar med data som i sin tur övergår till staten. Med varje statlig förändring finns det många specifika ögonblick där vissa tillgångar blir tillgängliga.

I en instans kan mallen vara klar, i en annan data kommer uppladdningen att slutföras. Kodning för varje instans kräver ett sätt att upptäcka. Livscykelkrokar svarar på detta behov. Moderna front-end ramar paketerar sig med en mängd olika livscykel krokar. Vinkel är inget undantag

Lifecycle Hooks Explained

Livscykelkrokar är tidsinställda metoder. De skiljer sig åt när och varför de kör. Ändringsdetektering utlöser dessa metoder. De körs beroende på förhållandena för den aktuella cykeln. Vinklade körningar ändrar detektering ständigt på dess data. Livscykelkrokar hjälper till att hantera dess effekter.

En viktig aspekt av dessa krokar är deras order. Det avviker aldrig. De körs baserat på en förutsägbar serie av belastningshändelser producerade från en detektionscykel. Detta gör dem förutsägbara.

Vissa tillgångar är bara tillgängliga efter att en viss krok har körts. Naturligtvis kör en krok endast under vissa förhållanden som anges i den aktuella ändringsdetekteringscykeln.

Den här artikeln presenterar livscykelkrokarna i ordning efter utförande (om de alla kör). Vissa villkor förtjänar att en krok aktiveras. Det finns några som bara kör en gång efter komponentinitialisering.

Alla livscykelmetoder är tillgängliga från @angular/core. Även om det inte krävs, rekommenderar Angular att varje krok implementeras. Denna övning leder till bättre felmeddelanden angående komponenten.

Beställning av Lifecycle Hooks 'Execution

ngOnChanges

ngOnChangestriggers efter modifieringen av @Inputbundna klassmedlemmar. Data bunden av @Input()dekoratören kommer från en extern källa. När den externa källan ändrar data på ett detekterbart sätt passerar den @Inputigen genom egenskapen.

Med den här uppdateringen ngOnChangesavfyras omedelbart. Det avfyras också vid initialisering av indata. Kroken får en valfri parameter av typen SimpleChanges. Detta värde innehåller information om de ändrade ingångsbundna egenskaperna.

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

Sammanfattning: ParentComponent binder indata till ChildComponent. Komponenten tar emot dessa data via sin@Inputegendom. ngOnChangesbränder. Efter fem sekundersetTimeoututlöses återuppringningen. ParentComponent muterar datakällan för ChildComponents ingångsbundna egenskap. Den nya datan flödar genom inmatningsegenskapen. ngOnChangesbränder ännu en gång.

ngOnInit

ngOnInitutlöses en gång efter initialisering av en komponents ingångsbundna ( @Input) egenskaper. Nästa exempel kommer att likna det sista. Kroken utlöses inte eftersom ChildComponent tar emot indata. Snarare avfyras det direkt efter att data har gjorts till ChildComponent-mallen.

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

Sammanfattning: ParentComponent binder indata till ChildComponent. ChildComponent tar emot dessa uppgifter via sin@Inputegendom. Datan återges till mallen. ngOnInitbränder. Efter fem sekundersetTimeoututlöses återuppringningen. ParentComponent muterar datakällan för ChildComponents ingångsbundna egenskap. ngOnInit BRANDAR INTE .

ngOnInitär en enstaka krok. Initialisering är det enda problemet.

ngDoCheck

ngDoCheckavfyrar vid varje ändringsdetekteringscykel. Vinklade körningar ändrar detektering ofta. Om du utför någon åtgärd kommer den att cykla. ngDoCheckbränder med dessa cykler. Använd den med försiktighet. Det kan skapa prestandaproblem när de implementeras felaktigt.

ngDoChecklåter utvecklare kontrollera deras data manuellt. De kan utlösa ett nytt ansökningsdatum villkorligt. I kombination med ChangeDetectorRefkan utvecklare skapa sina egna kontroller för ändringsdetektering.

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

Var uppmärksam på konsolen kontra skärmen. Uppgifterna går fram till "mellanliggande" innan frysning. Tre omgångar med ändringsdetektering inträffar under denna period som anges i konsolen. Ytterligare en omgång av förändringsdetektering inträffar när "final" skjuts till slutet av this.data. En sista omgång av ändringsdetektering inträffar sedan. Utvärderingen av if-uttalandet avgör att inga uppdateringar av vyn är nödvändiga.

Sammanfattning: Klassen startar efter två omgångar med förändringsdetektering. Klasskonstruktören initierarsetTimeouttvå gånger. Efter tre sekundersetTimeoututlöserden förstaändringsdetekteringen. ngDoCheckmarkerar skärmen för en uppdatering. Tre sekunder senaresetTimeoututlöserden andraändringsdetekteringen. Inga visningsuppdateringar behövs enligt utvärderingen avngDoCheck.

Varning

Innan du fortsätter ska du lära dig skillnaden mellan innehållet DOM och visa DOM (DOM står för Document Object Model).

Innehållet DOM definierar den inre HTML av direktivelement. Omvänt är vyn DOM en komponents mall exklusive alla HTML-mallar som är kapslade i ett direktiv. För en bättre förståelse, se detta blogginlägg.

ngAfterContentInit

ngAfterContentInitutlöses efter komponentens innehåll DOM initialiseras (laddas för första gången). Att vänta på @ContentChild(ren)frågor är krokens primära användningsfall.

@ContentChild(ren)frågor ger elementreferenser för innehållet DOM. Som sådan är de inte tillgängliga förrän innehållet DOM laddas. Därför används varför ngAfterContentInitoch dess motsvarighet ngAfterContentChecked.

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

De @ContentChildfrågeresultat är tillgängliga från ngAfterContentInit. Renderer2uppdaterar innehållet DOM för BComponent som innehåller en h3tagg och CComponent. Detta är ett vanligt exempel på innehållsprojektion.

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. ngAfterContentInit fires. BComponent finishes rendering. AComponent finishes rendering. ngAfterContentInit will not fire again.

ngAfterContentChecked

ngAfterContentChecked fires after every cycle of change detection targeting the content DOM. This lets developers facilitate how the content DOM reacts to change detection. ngAfterContentChecked can fire frequently and cause performance issues if poorly implemented.

ngAfterContentChecked fires during a component’s initialization stages too. It comes right after 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