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:
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:

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

Ś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:

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.