Angular – cykl życia komponentu

Opublikowane przez lewy w dniu

Angular dla komponentów, czyli podstawowych elementów budowy aplikacji udostępnia dość pokaźny zestaw tzw. hooków (uchwytów?), dzięki którym możemy reagować w interesujących nas momentach cyklu życia komponentu (Angular component lifecycle) na zdarzenia zachodzące w danym komponencie.

Hooków jest 8, wyglądają następująco i odpalane są w następującej kolejności

Przed hookami uruchamiany jest jeszcze konstruktor klasy komponentu.

ngOnChanges – Pierwszym uruchamianym hookiem (co nie musi być tak oczywiste) jest ngOnChanges. Funkcja ta uruchamiana jest gdy dla pól klasy oznaczone dekoratorem @Input() zmienią się wartości (jeżeli klasa nie posiada pól oznaczonych @Input() ngOnChanges nie jest uruchamiany)

ngOnChanges(changes: SimpleChanges) {
  for (let propName in changes) {
    console.log(propName, changes[propName].previousValue, changes[propName].currentValue)
  }
}
// ---
class SimpleChange {
  constructor(previousValue: any, currentValue: any)
  previousValue : any
  currentValue : any
  isFirstChange() : boolean
}

jest to jedyna metoda z cyklu życia komponentu, która ma argument, argument ten to obiekt którego kluczem jest nazwa zmiennej z dekoratorem @Input() a wartością jest klasa SimpleChange która zawiera 3 pola – aktualną wartość dla zmiennej, poprzednią wartość zmiennej i czy jest to wartość początkowa (pierwsza).

ngOnInit – Metoda ta jest uruchamiana raz, zaraz po konstruktorze klasy oraz pierwszym wywołaniu ngOnChanges (jeżeli istnieją pola oznaczone @Input()). Metoda ta jest dobrym miejscem do nadania początkowych wartości polom, wywołaniu funkcji które powinny wykonać się po utworzeniu komponentu itp. Dobre praktyki mówią, że konstruktor klasy powinien być wykorzystany tylko do wstrzyknięcia zależności, a wszystko inne powinno odbyć się w tej metodzie, jednak nie jest to warunek konieczny.

ngDoCheck – Metoda ta jest uruchamiana za każdym razem gdy zmienią się jakiekolwiek własności komponentu (nie tylko gdy zmianie ulegną pola oznaczone @Input() ale też każde inne). Należy rozsądnie korzystać z tej funkcji, ponieważ reagowanie w nieodpowiedni sposób może znacząco spowolnić działanie aplikacji.

ngOnDestroy – Metoda ta uruchamiana jest jako ostatnia przed usunięciem komponentu z drzewa DOM. Jest to najlepsze miejsce do „posprzątania” po sobie, zwolnienia zasobów, które mogą nie zostać zwolnione przez garbage collector (np. unsubscribe na observable).

Podane wyżej metody są najczęściej wykorzystywanymi i zarazem najbardziej intuicyjnymi. Angular dostarcza nam jeszcze 4 funkcje do reagowania na zdarzenia występujące w trakcie życia komponentu.

ngAfterContentInit – Metoda ta uruchomi się po zainicjowaniu <ng-content> i zrobi to raz po pierwszym wywołaniu metody ngDoCheck. Żeby być dokładniejszym, metoda ta uruchomi się kiedy Angular przeniesie zawartość (content) z zewnątrz do komponentu aktualnie tworzonego.

ngAfterContentChecked – Również reaguje na <ng-content> jednak nie tylko przy inicjalizacji (po metodzie ngAfterContentInit) komponentu ale za każdym razem gdy wstrzyknięty kontent ulegnie zmianie. Temat ng-content jest na tyle ciekawy, że pewnie pojawi się w osobnym wpisie.

ngAfterViewInit – Metoda uruchomi się po utworzeniu wszystkich komponentów dzieci tworzonego komponentu. Metoda ta uruchomi się raz po pierwszym wywołaniu ngAfterContentChecked.

ngAfterViewChecked – Metoda ta uruchomi się po każdej zmianie w komponentach dzieciach danego komponentu. Pierwsze jej uruchomienie nastąpi po ngAfterViewInit. Metodę tę możemy wykorzystać, gdy interesuje nas że jakieś elementy dziecka naszego komponentu uległy zmianie.

Aby zacząć używać opisanych wyżej hooków wystarczy je zaimplementować w kodzie naszego komponentu

export class MyComponent {
  constructor() { }
  ngAfterContentChecked(): void {
    console.log('ngAfterContentChecked');
  }
  ngAfterContentInit(): void {
    console.log('ngAfterContentInit');
  }
  ngAfterViewChecked(): void {
    console.log('ngAfterViewChecked');
  }
  ngAfterViewInit(): void {
    console.log('ngAfterViewInit');
  }
  ngDoCheck(): void {
    console.log('ngDoCheck');
  }
  ngOnChanges(changes: SimpleChanges): void {
      console.log('ngOnChanges');
  }
  ngOnInit(): void {
    console.log('ngOnInit');
  }
  ngOnDestroy(): void {
    console.log('ngOnDestroy');
  }
}

Jednak dobrą praktyką jest stosowanie interfejsów dostarczonych przez Angulara

export class MyComponent 
  implements OnInit,
    OnChanges,
    DoCheck,
    OnDestroy,
    AfterContentInit,
    AfterContentChecked,
    AfterViewInit,
    AfterViewChecked
{ ... }