Szögletes életciklus-kampók: ngOnChanges, ngOnInit és még sok más

Miért van szükségünk életciklus-horgokra?

A modern front-end keretrendszerek az alkalmazást állapotról államra mozgatják. Az adatok táplálják ezeket a frissítéseket. Ezek a technológiák kölcsönhatásba lépnek az adatokkal, amelyek viszont átváltják az állapotot. Minden állapotváltozás során sok olyan konkrét pillanat fordul elő, amikor bizonyos eszközök elérhetővé válnak.

Az egyik esetben a sablon készen áll, egy másik esetben az adatok feltöltése befejeződött. Az egyes példányok kódolása észlelési eszközt igényel. Az életciklus-horgok megválaszolják ezt az igényt. A modern front-end keretek különféle életciklus-horgokkal vannak felszerelve. A szögletes sem kivétel

Életciklus-horgok elmagyarázva

Az életciklus-horgok időzített módszerek. Abban különböznek, hogy mikor és miért hajtanak végre. A változás észlelése beindítja ezeket a módszereket. Az aktuális ciklus körülményeitől függően hajtják végre. A szögletes változások észlelése folyamatosan változik az adatain. Az életciklus-horgok segítenek kezelni annak hatásait.

E horgok fontos szempontja a végrehajtás sorrendje. Soha nem tér el. A detektálási ciklusból előállított terhelési események kiszámítható sorozata alapján hajtanak végre. Ez kiszámíthatóvá teszi őket.

Egyes eszközök csak egy bizonyos kampó végrehajtása után állnak rendelkezésre. Természetesen egy kampó csak bizonyos feltételek mellett hajtható végre az aktuális változásfelismerési ciklusban.

Ez a cikk bemutatja az életciklus-kampókat végrehajtásuk sorrendjében (ha mind végrehajtják). Bizonyos körülmények között érdemes aktiválni a horgot. Van néhány, aki csak egyszer hajt végre alkatrész inicializálás után.

Minden életciklus-módszer elérhető @angular/core. Bár nem szükséges, az Angular minden kampó bevezetését javasolja. Ez a gyakorlat jobb hibaüzenetekhez vezet az alkatrészt illetően.

Az életciklus-horgok végrehajtásának sorrendje

ngOnChanges

ngOnChangeskiváltja a @Inputbekötött osztálytagok módosítását követően . A @Input()dekorátor által kötött adatok külső forrásból származnak. Amikor a külső forrás ezeket az adatokat detektálható módon megváltoztatja, újra átmegy a @Inputtulajdonságon.

Ezzel a frissítéssel ngOnChangesazonnal elindul. A bemeneti adatok inicializálása után is tüzel. A horog egy opcionális típusú paramétert kap SimpleChanges. Ez az érték információkat tartalmaz a megváltozott bemenethez kötött tulajdonságokról.

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

Összegzés: A ParentComponent a bemeneti adatokat a ChildComponenthez köti. Az összetevő a@Inputtulajdonságánkeresztül kapja meg ezeket az adatokat. ngOnChangestüzek. Öt másodperc múlva asetTimeoutvisszahívás aktiválódik. A ParentComponent mutálja a ChildComponent bemenethez kötött tulajdonságának adatforrását. Az új adatok a bemeneti tulajdonságon keresztül áramlanak. ngOnChangesmegint tüzek.

ngOnInit

ngOnInita komponens bemenethez kötött ( @Input) tulajdonságainak inicializálása után egyszer bekapcsol . A következő példa hasonló lesz az előzőhöz. A horog nem indul el, mivel a ChildComponent megkapja a bemeneti adatokat. Inkább azonnal elindul, miután az adatok a ChildComponent sablonba kerülnek.

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

Összegzés: A ParentComponent a bemeneti adatokat a ChildComponenthez köti. A ChildComponent ezeket az adatokat a@Inputtulajdonánkeresztül kapja meg. Az adatok a sablonba kerülnek. ngOnInittüzek. Öt másodperc múlva asetTimeoutvisszahívás aktiválódik. A ParentComponent mutálja a ChildComponent bemenethez kötött tulajdonságának adatforrását. Az ngOnInit NEM TŰZ .

ngOnInitegy egyszerû horog. Az inicializálás az egyetlen gond.

ngDoCheck

ngDoCheckminden változás észlelési ciklusnál tüzel. A szögfutások gyakran észlelik a változás érzékelését. Bármely művelet végrehajtása ciklust okoz. ngDoCheckezekkel a ciklusokkal tüzel. Óvatosan használja. Helytelen megvalósítás esetén teljesítményproblémákat okozhat.

ngDoChecklehetővé teszi a fejlesztők számára, hogy manuálisan ellenőrizzék adataikat. Feltételesen kiválthatják az új alkalmazás dátumát. Ezzel együtt a ChangeDetectorReffejlesztők létrehozhatják saját ellenőrzéseiket a változás észlelésére.

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

Ügyeljen a konzolra és a kijelzőre. Az adatok fagyás előtt „közbensőre” haladnak. A változás észlelésének három fordulója történik ebben az időszakban, ahogyan azt a konzol jelzi. Még egy kör a változás észlelésére akkor kerül sor, amikor a „végső” a végére tolódik this.data. Ezután a változás észlelésének utolsó fordulója következik be. Az if utasítás értékelése szerint nincs szükség a nézet frissítésére.

Summary: Class instantiates after two rounds of change detection. Class constructor initiates setTimeout twice. After three seconds, the first setTimeout triggers change detection. ngDoCheck marks the display for an update. Three seconds later, the second setTimeout triggers change detection. No view updates needed according to the evaluation of ngDoCheck.

Warning

Before proceeding, learn the difference between the content DOM and view DOM (DOM stands for Document Object Model).

The content DOM defines the innerHTML of directive elements. Conversely, the view DOM is a component’s template excluding any template HTML nested within a directive. For a better understanding, refer to this blog post.

ngAfterContentInit

ngAfterContentInit fires after the component’s content DOM initializes (loads for the first time). Waiting on @ContentChild(ren) queries is the hook’s primary use-case.

@ContentChild(ren) queries yield element references for the content DOM. As such, they are not available until after the content DOM loads. Hence why ngAfterContentInit and its counterpart ngAfterContentChecked are used.

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

The @ContentChild query results are available from ngAfterContentInit. Renderer2 updates the content DOM of BComponent containing a h3 tag and CComponent. This is a common example of content projection.

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