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
ngOnChanges
kiváltja a @Input
bekö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 @Input
tulajdonságon.
Ezzel a frissítéssel ngOnChanges
azonnal 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@Input
tulajdonságánkeresztül kapja meg ezeket az adatokat. ngOnChanges
tüzek. Öt másodperc múlva asetTimeout
visszahí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. ngOnChanges
megint tüzek.
ngOnInit
ngOnInit
a 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@Input
tulajdonánkeresztül kapja meg. Az adatok a sablonba kerülnek. ngOnInit
tüzek. Öt másodperc múlva asetTimeout
visszahí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 .
ngOnInit
egy egyszerû horog. Az inicializálás az egyetlen gond.
ngDoCheck
ngDoCheck
minden 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. ngDoCheck
ezekkel a ciklusokkal tüzel. Óvatosan használja. Helytelen megvalósítás esetén teljesítményproblémákat okozhat.
ngDoCheck
lehető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 ChangeDetectorRef
fejlesztő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