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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
@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:
1 2 3 4 5 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
@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:
1 |
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.
Pingback: ANGULAR 2 CUSTOM VALIDATORS