Kutne kuke životnog ciklusa: ngOnChanges, ngOnInit i još mnogo toga

Zašto su nam potrebne kuke životnog ciklusa?

Suvremeni front-end okviri premještaju aplikaciju iz stanja u stanje. Podaci potiču ta ažuriranja. Te tehnologije komuniciraju s podacima koji zauzvrat prelaze u stanje. Sa svakom promjenom države postoje mnogi specifični trenuci kada određena imovina postaje dostupna.

U jednom je slučaju predložak možda spreman, a u drugom će prijenos podataka biti završen. Kodiranje za svaku instancu zahtijeva sredstva za otkrivanje. Kuke životnog ciklusa odgovaraju na ovu potrebu. Moderni front-end okviri pakiraju se s raznim kukama životnog ciklusa. Kutni nije iznimka

Objašnjene kuke životnog ciklusa

Kuke životnog ciklusa su vremenske metode. Razlikuju se kada i zašto izvršavaju. Otkrivanje promjena pokreće ove metode. Izvode se ovisno o uvjetima trenutnog ciklusa. Kutna pokretanja neprestano otkrivaju promjene na svojim podacima. Kuke životnog ciklusa pomažu u upravljanju njegovim učincima.

Važan aspekt ovih kuka je redoslijed izvršenja. Nikad ne odstupa. Izvršavaju se na temelju predvidljive serije događaja opterećenja proizvedenih iz ciklusa detekcije. To ih čini predvidljivima.

Neka sredstva dostupna su samo nakon izvršenja određene kuke. Naravno, kuku izvršiti samo pod određenim uvjetima postavljenim u trenutnom ciklusu otkrivanja promjena.

Ovaj članak predstavlja kuke životnog ciklusa po redoslijedu njihova izvršenja (ako se sve izvršavaju). Određeni uvjeti zaslužuju aktivaciju udice. Nekoliko je onih koji izvršavaju samo jednom nakon inicijalizacije komponente.

Sve metode životnog ciklusa dostupne su na @angular/core. Iako nije potrebno, Angular preporučuje primjenu svake udice. Ova praksa dovodi do boljih poruka o pogreškama u vezi s komponentom.

Nalog izvršenja kuka za životni ciklus

ngOnChanges

ngOnChangesokidači nakon izmjene @Inputpovezanih članova razreda. Podaci koje @Input()dekorater veže potječu iz vanjskog izvora. Kada vanjski izvor promijeni te podatke na prepoznatljiv način, on ponovno prolazi kroz @Inputsvojstvo.

Ovim ažuriranjem ngOnChangesodmah se aktivira. Također se aktivira nakon inicijalizacije ulaznih podataka. Kuka prima jedan neobavezni parametar tipa SimpleChanges. Ova vrijednost sadrži informacije o promijenjenim svojstvima vezanim za ulaz.

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

Sažetak: ParentComponent veže ulazne podatke na ChildComponent. Komponenta prima ove podatke putem svog@Inputsvojstva. ngOnChangesvatre. Nakon pet sekundisetTimeoutaktivira se povratni poziv. ParentComponent mutira izvor podataka svojstva ChildComponent-a povezanog s unosom. Novi podaci teku kroz ulazno svojstvo. ngOnChangespožari još jednom.

ngOnInit

ngOnInitaktivira se jednom prilikom inicijalizacije @Inputsvojstava input-bound ( ) komponente . Sljedeći će primjer izgledati slično kao i posljednji. Kuka se ne aktivira jer ChildComponent prima ulazne podatke. Umjesto toga, aktivira se odmah nakon što se podaci generiraju u predložak 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); } }

Sažetak: ParentComponent veže ulazne podatke na ChildComponent. ChildComponent prima ove podatke putem svog@Inputsvojstva. Podaci se prikazuju u predlošku. ngOnInitvatre. Nakon pet sekundisetTimeoutaktivira se povratni poziv. ParentComponent mutira izvor podataka svojstva ChildComponent-a povezanog s unosom. ngOnInit NE PALI.

ngOnInitje jednokratna udica. Njegova je briga samo inicijalizacija.

ngDoCheck

ngDoCheckpali se svakim ciklusom otkrivanja promjena. Otkrivanje promjena u kutnim izvođenjima često se događa. Izvođenjem bilo koje radnje prouzročit će se ciklus. ngDoCheckpali tim ciklusima. Koristite ga oprezno. Može stvoriti probleme s izvedbom ako se pogrešno implementira.

ngDoCheckomogućuje programerima da ručno provjeravaju svoje podatke. Uvjetno mogu pokrenuti novi datum prijave. Zajedno s ChangeDetectorRefprogramerima mogu stvoriti vlastite provjere za otkrivanje promjena.

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

Obratite pažnju na konzolu u odnosu na zaslon. Podaci napreduju do 'srednje vrijednosti' prije zamrzavanja. Tijekom tog razdoblja događaju se tri kruga otkrivanja promjena kako je naznačeno na konzoli. Još jedan krug otkrivanja promjena događa se kad se 'konačni' pomakne do kraja this.data. Tada se događa posljednji krug otkrivanja promjena. Procjena izjave if utvrđuje da nisu potrebna ažuriranja prikaza.

Sažetak: Klasa se pokreće nakon dva kruga otkrivanja promjena. Konstruktor klase pokreće sesetTimeoutdva puta. Nakon tri sekunde, prvisetTimeoutokidači otkrivaju promjene. ngDoCheckoznačava zaslon za ažuriranje. Tri sekunde kasnije, drugisetTimeoutokidači otkrivaju promjene. Nisu potrebna ažuriranja pregleda prema procjeningDoCheck.

Upozorenje

Prije nastavka naučite razliku između sadržaja DOM i prikaza DOM (DOM označava objektni model dokumenta).

Sadržaj DOM definira unutarnji HTML elemenata direktiva. Suprotno tome, prikaz DOM je predložak komponente koji isključuje bilo koji HTML predložak ugniježđen unutar direktive. Za bolje razumijevanje pogledajte ovaj post na blogu.

ngAfterContentInit

ngAfterContentInitaktivira se nakon pokretanja sadržaja komponente DOM (učitava se prvi put). Čekanje na @ContentChild(ren)upite primarni je slučaj upotrebe udice.

@ContentChild(ren)upiti daju reference elemenata za sadržaj DOM. Kao takvi, dostupni su tek nakon učitavanja sadržaja DOM-a. Otuda se zašto ngAfterContentIniti njegov pandan ngAfterContentCheckedkoriste.

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

Rezultati @ContentChildupita dostupni su na ngAfterContentInit. Renderer2ažurira sadržaj DOM BComponenta koji sadrži h3oznaku i CComponent. Ovo je čest primjer projekcije sadržaja.

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