De ce avem nevoie de cârlige pentru ciclul de viață?

Cadrele front-end moderne mută aplicația de la stat la stat. Datele alimentează aceste actualizări. Aceste tehnologii interacționează cu datele care la rândul lor tranziționează starea. Cu fiecare schimbare de stare, există multe momente specifice în care anumite active devin disponibile.

La un moment dat, șablonul ar putea fi gata, în altul, datele vor fi finalizate. Codificarea pentru fiecare instanță necesită un mijloc de detectare. Cârligele ciclului de viață răspund acestei nevoi. Cadrele front-end moderne se împachetează cu o varietate de cârlige pentru ciclul de viață. Unghiular nu face excepție

Cârligele ciclului de viață explicate

Cârligele ciclului de viață sunt metode temporizate. Ele diferă când și de ce execută. Detectarea modificărilor declanșează aceste metode. Acestea se execută în funcție de condițiile ciclului curent. Rularea unghiulară detectează în mod constant modificările datelor sale. Cârligele ciclului de viață ajută la gestionarea efectelor sale.

Un aspect important al acestor cârlige este ordinea lor de execuție. Nu se abate niciodată. Acestea se execută pe baza unei serii predictibile de evenimente de încărcare produse dintr-un ciclu de detectare. Acest lucru le face previzibile.

Unele active sunt disponibile numai după executarea unui anumit cârlig. Desigur, un cârlig se execută numai în anumite condiții stabilite în ciclul curent de detectare a modificărilor.

Acest articol prezintă cârligele ciclului de viață în ordinea executării lor (dacă toate execută). Anumite condiții merită activarea unui cârlig. Există câțiva care execută o singură dată după inițializarea componentei.

Toate metodele ciclului de viață sunt disponibile de la @angular/core. Deși nu este necesar, unghiular recomandă implementarea fiecărui cârlig. Această practică duce la mesaje de eroare mai bune cu privire la componentă.

ad-banner

Ordinul executării cârligelor ciclului de viață

ngOnChanges

ngOnChanges declanșează în urma modificării @Input membrii clasei legați. Date legate de @Input() decoratorul provine dintr-o sursă externă. Când sursa externă modifică aceste date într-un mod detectabil, acestea trec prin @Input proprietate din nou.

Cu această actualizare, ngOnChanges trage imediat. De asemenea, se declanșează la inițializarea datelor de intrare. Cârligul primește un parametru opțional de tip SimpleChanges. Această valoare conține informații despre proprietățile modificate legate de intrare.

import { Component, Input, OnChanges } from '@angular/core';

@Component({
  selector: 'app-child',
  template: `
  <h3>Child Component</h3>
  <p>TICKS: {{ lifecycleTicks }}</p>
  <p>DATA: {{ data }}</p>
  `
})
export class ChildComponent implements OnChanges {
  @Input() data: string;
  lifecycleTicks: number = 0;

  ngOnChanges() {
    this.lifecycleTicks++;
  }
}

@Component({
  selector: 'app-parent',
  template: `
  <h1>ngOnChanges Example</h1>
  <app-child [data]="arbitraryData"></app-child>
  `
})
export class ParentComponent {
  arbitraryData: string = 'initial';

  constructor() {
    setTimeout(() => {
      this.arbitraryData="final";
    }, 5000);
  }
}

Rezumat: ParentComponent leagă datele de intrare de ChildComponent. Componenta primește aceste date prin @Input proprietate. ngOnChanges incendii. După cinci secunde, setTimeout declanșatoare de apel invers. ParentComponent mută sursa de date a proprietății legate de intrare ChildComponent. Noile date circulă prin proprietatea de intrare. ngOnChanges se aprinde din nou.

ngOnInit

ngOnInit se declanșează o dată la inițializarea legăturii de intrare a unei componente (@Input) proprietăți. Următorul exemplu va arăta similar cu ultimul. Cârligul nu se declanșează deoarece ChildComponent primește datele de intrare. Mai degrabă, se declanșează imediat după ce datele sunt redate în șablonul ChildComponent.

import { Component, Input, OnInit } from '@angular/core';

@Component({
  selector: 'app-child',
  template: `
  <h3>Child Component</h3>
  <p>TICKS: {{ lifecycleTicks }}</p>
  <p>DATA: {{ data }}</p>
  `
})
export class ChildComponent implements OnInit {
  @Input() data: string;
  lifecycleTicks: number = 0;

  ngOnInit() {
    this.lifecycleTicks++;
  }
}

@Component({
  selector: 'app-parent',
  template: `
  <h1>ngOnInit Example</h1>
  <app-child [data]="arbitraryData"></app-child>
  `
})
export class ParentComponent {
  arbitraryData: string = 'initial';

  constructor() {
    setTimeout(() => {
      this.arbitraryData="final";
    }, 5000);
  }
}

Rezumat: ParentComponent leagă datele de intrare de ChildComponent. ChildComponent primește aceste date prin intermediul său @Input proprietate. Datele sunt redate șablonului. ngOnInit incendii. După cinci secunde, setTimeout declanșatoare de apel invers. ParentComponent mută sursa de date a proprietății legate de intrare ChildComponent. ngOnInit NU INCENDIAZĂ.

ngOnInit este un cârlig unic. Inițializarea este singura sa preocupare.

ngDoCheck

ngDoCheck se aprinde cu fiecare ciclu de detectare a modificărilor. Rularea unghiulară modifică frecvent detectarea. Efectuarea oricărei acțiuni va determina ciclul acesteia. ngDoCheck se aprinde cu aceste cicluri. Folosiți-l cu precauție. Poate crea probleme de performanță atunci când este implementat incorect.

ngDoCheck permite dezvoltatorilor să își verifice manual datele. Pot declanșa o nouă dată a aplicației în mod condiționat. În legătură cu ChangeDetectorRef, dezvoltatorii își pot crea propriile verificări pentru detectarea modificărilor.

import { Component, DoCheck, ChangeDetectorRef } from '@angular/core';

@Component({
  selector: 'app-example',
  template: `
  <h1>ngDoCheck Example</h1>
  <p>DATA: {{ data[data.length - 1] }}</p>
  `
})
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();
    }
  }
}

Acordați atenție consolei față de ecran. Datele progresează până la „intermediare” înainte de înghețare. Trei runde de detectare a modificărilor au loc în această perioadă, așa cum este indicat în consolă. Încă o rundă de detectare a modificărilor are loc pe măsură ce „finalul” este împins până la sfârșitul anului this.data. Apare apoi o ultimă rundă de detectare a modificărilor. Evaluarea declarației if determină că nu sunt necesare actualizări ale vizualizării.

Rezumat: Clasa instantaneează după două runde de detectare a modificărilor. Constructorul de clase inițiază setTimeout de două ori. După trei secunde, prima setTimeout declanșează detectarea modificărilor. ngDoCheck marchează afișajul pentru o actualizare. Trei secunde mai târziu, a doua setTimeout declanșează detectarea modificărilor. Nu sunt necesare actualizări de vizualizare în funcție de evaluarea ngDoCheck.

Avertizare

Înainte de a continua, aflați diferența dintre conținutul DOM și vizualizarea DOM (DOM înseamnă Document Object Model).

Conținutul DOM definește HTML-ul interior al elementelor directive. În schimb, vizualizarea DOM este un șablon al unei componente, excluzând orice șablon HTML imbricat într-o directivă. Pentru o mai bună înțelegere, consultați această postare de blog.

ngAfterContentInit

ngAfterContentInit se declanșează după inițializarea conținutului componentei DOM (se încarcă pentru prima dată). În așteptare @ContentChild(ren) interogări este cazul de utilizare principal al cârligului.

@ContentChild(ren) interogările dau referințe la elemente pentru conținutul DOM. Ca atare, acestea nu sunt disponibile decât după încărcarea conținutului DOM. De aici de ce ngAfterContentInit și omologul său ngAfterContentChecked sunt folosite.

import { Component, ContentChild, AfterContentInit, ElementRef, Renderer2 } from '@angular/core';

@Component({
  selector: 'app-c',
  template: `
  <p>I am C.</p>
  <p>Hello World!</p>
  `
})
export class CComponent { }

@Component({
  selector: 'app-b',
  template: `
  <p>I am B.</p>
  <ng-content></ng-content>
  `
})
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: `
  <h1>ngAfterContentInit Example</h1>
  <p>I am A.</p>
  <app-b>
    <h3 #BHeader>BComponent Content DOM</h3>
    <app-c></app-c>
  </app-b>
  `
})
export class AComponent { }

@ContentChild rezultatele interogării sunt disponibile de la ngAfterContentInit. Renderer2 actualizează conținutul DOM al BComponent care conține un h3 tag și componentă CC. Acesta este un exemplu obișnuit de proiecție de conținut.

Rezumat: Redarea începe cu AComponent. Pentru ca acesta să se termine, AComponent trebuie să redea BComponent. BComponent proiectează conținut imbricat în elementul său prin intermediul <ng-content></ng-content> element. CComponent face parte din conținutul proiectat. Conținutul proiectat finalizează redarea. ngAfterContentInit incendii. BComponent termină redarea. AComponent termină redarea. ngAfterContentInit nu va mai trage.

ngAfterContentChecked

ngAfterContentChecked se declanșează după fiecare ciclu de detectare a modificărilor care vizează conținutul DOM. Acest lucru permite dezvoltatorilor să faciliteze modul în care conținutul DOM reacționează la detectarea modificărilor. ngAfterContentChecked poate declanșa frecvent și poate cauza probleme de performanță dacă este implementat prost.

ngAfterContentChecked se declanșează și în timpul etapelor de inițializare a unei componente. Vine imediat după ngAfterContentInit.

import { Component, ContentChild, AfterContentChecked, ElementRef, Renderer2 } from '@angular/core';

@Component({
  selector: 'app-c',
  template: `
  <p>I am C.</p>
  <p>Hello World!</p>
  `
})
export class CComponent { }

@Component({
  selector: 'app-b',
  template: `
  <p>I am B.</p>
  <button (click)="$event">CLICK</button>
  <ng-content></ng-content>
  `
})
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: `
  <h1>ngAfterContentChecked Example</h1>
  <p>I am A.</p>
  <app-b>
    <h3 #BHeader>BComponent Content DOM</h3>
    <app-c></app-c>
  </app-b>
  `
})
export class AComponent { }

Acest lucru diferă cu greu de ngAfterContentInit. O simplă <button></button> a fost adăugat la BComponent. Făcând clic pe acesta se produce o buclă de detectare a modificărilor. Acest lucru activează cârligul așa cum este indicat prin randomizarea lui background-color.

Rezumat: Redarea începe cu AComponent. Pentru ca acesta să se termine, AComponent trebuie să redea BComponent. BComponent proiectează conținut imbricat în elementul său prin intermediul <ng-content></ng-content> element. CComponent face parte din conținutul proiectat. Conținutul proiectat finalizează redarea. ngAfterContentChecked incendii. BComponent termină redarea. AComponent termină redarea. ngAfterContentChecked poate declanșa din nou prin detectarea modificărilor.

ngAfterViewInit

ngAfterViewInit se declanșează o dată după terminarea inițializării DOM. Vizualizarea se încarcă întotdeauna imediat după conținut. ngAfterViewInit așteaptă @ViewChild(ren) interogări de rezolvat. Aceste elemente sunt interogate din aceeași vizualizare a componentei.

În exemplul de mai jos, BComponent’s h3 antetul este interogat. ngAfterViewInit se execută imediat ce rezultatele interogării sunt disponibile.

import { Component, ViewChild, AfterViewInit, ElementRef, Renderer2 } from '@angular/core';

@Component({
  selector: 'app-c',
  template: `
  <p>I am C.</p>
  <p>Hello World!</p>
  `
})
export class CComponent { }

@Component({
  selector: 'app-b',
  template: `
  <p #BStatement>I am B.</p>
  <ng-content></ng-content>
  `
})
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: `
  <h1>ngAfterViewInit Example</h1>
  <p>I am A.</p>
  <app-b>
    <h3>BComponent Content DOM</h3>
    <app-c></app-c>
  </app-b>
  `
})
export class AComponent { }

Renderer2 modifică culoarea de fundal a antetului BComponent. Acest lucru indică faptul că elementul de vizualizare a fost interogat cu succes datorită ngAfterViewInit.

Rezumat: Redarea începe cu AComponent. Pentru ca acesta să se termine, AComponent trebuie să redea BComponent. BComponent proiectează conținut imbricat în elementul său prin intermediul <ng-content></ng-content> element. CComponent face parte din conținutul proiectat. Conținutul proiectat finalizează redarea. BComponent termină redarea. ngAfterViewInit incendii. AComponent termină redarea. ngAfterViewInit nu va mai trage.

ngAfterViewChecked

ngAfterViewChecked se declanșează după orice ciclu de detectare a modificărilor care vizează vizualizarea componentei. ngAfterViewChecked hook permite dezvoltatorilor să faciliteze modul în care detectarea modificărilor afectează DOM-ul de vizualizare.

import { Component, ViewChild, AfterViewChecked, ElementRef, Renderer2 } from '@angular/core';

@Component({
  selector: 'app-c',
  template: `
  <p>I am C.</p>
  <p>Hello World!</p>
  `
})
export class CComponent { }

@Component({
  selector: 'app-b',
  template: `
  <p #BStatement>I am B.</p>
  <button (click)="$event">CLICK</button>
  <ng-content></ng-content>
  `
})
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: `
  <h1>ngAfterViewChecked Example</h1>
  <p>I am A.</p>
  <app-b>
    <h3>BComponent Content DOM</h3>
    <app-c></app-c>
  </app-b>
  `
})
export class AComponent { }

Rezumat: Redarea începe cu AComponent. Pentru ca acesta să se termine, AComponent trebuie să redea BComponent. BComponent proiectează conținut imbricat în elementul său prin intermediul <ng-content></ng-content> element. CComponent face parte din conținutul proiectat. Conținutul proiectat finalizează redarea. BComponent termină redarea. ngAfterViewChecked incendii. AComponent termină redarea. ngAfterViewChecked poate declanșa din nou prin detectarea modificărilor.

Dând clic pe <button></button> elementul inițiază o rundă de detectare a modificărilor. ngAfterContentChecked trage și randomizează background-color din elementele interogate, fiecare buton face clic.

ngOnDestroy

ngOnDestroy se declanșează la eliminarea unei componente din vizualizare și DOM ulterioare. Acest cârlig oferă șansa de a curăța orice capete libere înainte de ștergerea unei componente.

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: `
  <h1>ngOnDestroy Example</h1>
  <button (click)="toggleDestroy()">TOGGLE DESTROY</button>
  <p appDestroyListener *ngIf="destroy">I can be destroyed!</p>
  `
})
export class ExampleComponent {
  destroy: boolean = true;

  toggleDestroy() {
    this.destroy = !this.destroy;
  }
}

Rezumat: Se face clic pe buton. ExampleComponent destroy membru comută fals. Directiva structurală *ngIf evaluează la fals. ngOnDestroy incendii. *ngIf își înlătură gazda <p></p>. Acest proces se repetă de câte ori faceți clic pe butonul pentru a comuta destroy la fals.

Concluzie

Amintiți-vă că trebuie îndeplinite anumite condiții pentru fiecare cârlig. Vor executa întotdeauna în ordine una cu alta, indiferent. Acest lucru face ca cârligele să fie suficient de previzibile pentru a lucra, chiar dacă unele nu execută.

Cu cârligele ciclului de viață, sincronizarea execuției unei clase este ușoară. Acestea le permit dezvoltatorilor să urmărească unde are loc detectarea modificărilor și cum ar trebui să reacționeze aplicația. Acestea blochează codul care necesită dependențe bazate pe încărcare disponibile numai după o perioadă de timp.

Ciclul de viață al componentelor caracterizează cadrele front-end moderne. Angular își prezintă ciclul de viață oferind cârligele menționate mai sus.

Surse

Resurse