23 sty 2017
5 min

ANGULAR 2 Lifecycle Hooks

LIFECYCLE HOOKS – ngOnChanges & ngDoCheck

Zapewne wiesz, że komponenty mają swój cykl życia, składający się z hooków, w które możemy się wpiąć. W tym artykule skupię się na Lifecycle hooks czyli dokładnie 2 hookach: ngOnChanges oraz ngDoCheck, sprawdzimy jak możemy reagować na zmiany data-bound oraz poszczególnych właściwości obiektu.

Na starcie polecam spojrzeć do live example, ułatwi Ci to dalsze zrozumienie kodu:

LIVE EXAMPLE

ngOnChanges

NgOnChanges jest hookiem, od którego komponent rozpoczyna swoje życie. Uruchamia się w dwóch przypadkach:

  • raz przed ngOnInit, jako pierwszy hook cyklu życia komponentu
  • za każdym razem, kiedy zmieni się referencja do obiektu w data-bound (data-bound to np. [actor]=”John”), natomiast mutowanie obiektu nie zawoła ngOnChanges!

Co umożliwia ngChanges? Możemy uruchomić custom code, w momencie jak data-bound zmieni swoją zawartość (referencję do obiektu, którą przetrzymuje).

NgOnChanges przyjmuje jeden parametr typu SimpleChanges. Parametr daje nam dostęp do poprzedniej oraz aktualnej wartości dowolnego Inputa. Sprawdźmy to na przykładzie:

@Component({
 moduleId: module.id,
 selector: 'ng-on-changes',
 template: `
 <p> {{ user.firstName }} {{user.lastName}} {{user.age}}</p>
 `
})
export class OnChangesComponent implements OnChanges {
 @Input() user : any;
 
 constructor() {}
 
 ngOnChanges(changes : SimpleChanges) {
   console.log('ngOnChanges, previous User: ', changes.user.previousValue);
   console.log('ngOnChanges, next User: ', changes.user.currentValue);
   console.log('ngOnChanges, is first change?: ', changes.user.isFirstChange());
 }
}

Sięgamy po nasz OnChangesComponent w AppComponent, i dodamy dwie metody na buttonach:

@Component({
 moduleId: module.id,
 selector: 'my-app',
 template: `
   <div class="jumbotron">
     <h1 class="display-7">ngOnChanges & ngDoCheck with ANGULAR.LOVE!</h1>
     <p class="lead">This is example of ngOnChanges & ngDoCheck lifecycles hooks</p>
   </div>
   <button (click)="mutateUserObject()">Mutate User object</button> // ngOnChanges się nie wywoła
   <button (click)="changeReference()">Change reference</button> // ngOnChanges się odpali
   <ng-on-changes [user]="john"></ng-on-changes>
 `
})

export class AppComponent {
 
 john : any = {
   firstName: 'Johnny',
   lastName: 'Cage',
   age: 36
 }
 
 mutateUserObject() : void {
   this.john.firstName = 'Michael';
 }
 
 changeReference() : void {
   this.john = {
     firstName: 'Bonny',
     lastName: 'Kevin',
     age: 12,
     city: 'New York'
   }
 }
}

Oraz nasze logi przy tworzeniu komponentu, ale jeszcze przed ngOnInit:

Lifecycle Hooks - log przed ngOnInit

oraz po kliknięciu buttona Change Reference, który podmienił nam obiekt:

Lifecycle Hooks - log po change reference

Świetna sprawa, mamy dostęp do poprzedniej wartości Inputa oraz możemy złapać moment zmiany wartości. A co jeśli nie mamy do czynienia z Immutable data i chcemy wyłapać zmianę pojedynczego property obiektu? Z pomocą przychodzi nam ngDoCheck.

ngDoCheck

NgDoCheck jest kolejnym hookiem cyklu życia komponentu. Kiedy się z nim spotykamy?

  • uruchamia się bezpośrednio po ngOnInit
  • uruchamia się za każdym razem, kiedy odpala się Change Detection

Po co ten cały ngDoCheck? Rozszerza działanie change Detectora, który domyślnie sprawdza, czy zmieniła się tylko referencja w data-bound. Dzięki ngDoCheck możemy zareagować na zmianę danego property obiektu.

Przykład:

ngDoCheck() {
 if (this.user.firstName === 'Michael') {
   console.log('single property checked');
 }
}

Jeśli powyższy kod umieścimy w ngOnChanges(), nie będzie żadnego efektu.

KeyValueDiffers + ngDoCheck = ngDoCheck na sterydach

Jeśli chcemy mieć pełen dostęp do tego co się działo z właściwościami obiektu, to polecam połączyć ngDoCheck z klasą KeyValueDiffers. Sprawdźmy, jak to działa:

@Component({
 moduleId: module.id,
 selector: 'ng-do-check',
 template: `
 <p> {{ user.firstName }}, {{user.lastName}}, {{user.age}}</p>
 `
})
export class DoCheckComponent implements DoCheck {
 @Input() user : any;
 differ : any;
 
 constructor(private differs : KeyValueDiffers) {
   this.differ = differs.find({}).create(null);
 }

 ngDoCheck() {
   let changes = this.differ.diff(this.user);

   if (changes) {
     console.log('properties have changed');
     changes.forEachChangedItem(item => console.log('ngDoCheck, changed items ', item.currentValue));
     changes.forEachAddedItem(item => console.log('ngDoCheck, added items ' + item.currentValue));
     changes.forEachRemovedItem(item => console.log('ngDoCheck, removed items ' + item.currentValue));
   } else {
     console.log('no changes');
   }
 }
}

Jeśli zajrzysz do LIVE EXAMPLE i klikniesz button Change Reference, to w konsoli ujrzysz następujące logi:

Lifecycle Hooks - zmiana properties obiektu

Jak na dłoni widzimy jakie properties obiektu się zmieniły/pojawiły/usunęły.

Ok, do rzeczy. Czym jest KeyValueDiffers?

To serwis, który umożliwia nam śledzenie zmian dokonanych na właściwościach obiektu. Na jakie zmiany w obiekcie możemy reagować?

  • zmienione properties (forEachChangedIem)
  • usunięte properties (forEachRemovedItem)
  • dodane properties (forEachAddedItem)

Zapewne zwróciłeś uwagę na kod w konstruktorze:

this.differ = differs.find({}).create(null);

W tej linijce tworzymy tzw. differa. Metoda find({}) daje nam odpowiedniego differa:

  • find({}) dla „object differ”, jeśli chcemy łapać różnice w obiekcie
  • find([]) dla „array differ”, jeśli chcemy łapać różnice w tablicy

Natomiast metoda create(null) przypisuje do differa wartość początkową null, bez tego byśmy nie złapali różnic w pierwszym użyciu.

PODSUMOWANIE LIFECYCLE HOOKS

Podstawowa różnica między ngOnChanges & ngDoCheck:

  • ngOnChanges NIE reaguje na zmiany danego property, wywoła się wyłącznie kiedy w data-bound (Inpucie) zmieni się refrencja do obiektu
  • ngDoCheck pozwala nam reagować na zmianę pojedynczego property, wywoła się zawsze przy uruchomieniu systemu detekcji, więc częste używanie ngDoCheck może negatywnie wpłynąć na performance aplikacji

O czym należy pamiętać:

  • Połącznie ngOnChanges i ngDoCheck trzeba zrobić z głową lub w ogóle nie łączyć, z racji, że ngOnChanges będzie dalej wołany jak Change Detector wyłapie zmianę zawartości w data-bound, co doprowadzi do tego, że będziemy wołać jednocześnie ngDoCheck i ngOnChanges.
Podziel się artykułem

Zapisz się na nasz newsletter

Dołącz do community Angular.love i bądź na bieżąco z trendami.