/ ANGULAR

Angular 강좌(20) - Component LifeCycle

Angular 강좌는 여러 절로 구성되어 있습니다.


Component Lifecycle

이번 포스트에서는 Angular의 Lifecycle에 대해서 알아보겠습니다. 다른 Framework과 마찬가지로 Angular 역시 여러 단계의 lifecycle을 관리합니다. Component와 Directive가 이 lifecycle의 영향을 받게 되며 각 lifecycle마다 제공되는 hook method를 이용하여 특정 작업을 처리할 수 있습니다.

이런 hook method는 interface형태로 우리에게 제공됩니다. Component 혹은 Directive class가 이 interface를 구현하고 그 안의 특정 method를 overriding하는 식으로 hook method를 이용할 수 있습니다.

Component를 대상으로 객체가 생성되고 소멸되기까지 호출되는 hook method를 순서대로 나열하면 다음과 같습니다. Directive는 View를 가지고 있지 않기 때문에 ngAfter로 시작되는 hook method는 호출되지 않습니다.

  • constructor
  • ngOnChanges
  • ngOnInit
  • ngDoCheck
  • ngAfterContentInit
  • ngAfterContentChecked
  • ngAfterViewInit
  • ngAfterViewChecked
  • ngOnDestroy

간단하게 Project를 하나 생성해서 각 lifecycle단계에서 해당 hook method가 호출되는지 확인하는 식으로 진행하시면 됩니다.


constructor

Component 혹은 Directive가 생성될 때 호출됩니다. 사실 constructor는 Angular의 lifecycle의 단계에 포함될 내용은 아닙니다.

TypeScript에서는 일반적으로 constructor에서 초기화를 진행합니다. 하지만 Angular에서 사용하는 속성의 초기화는 ngOnInit에서 하는것이 좋습니다.


ngOnChanges

@Input을 이용해 부모 Component가 자식 Component에게 데이터를 전달할 수 있습니다. ngOnChanges는 부모 Component에서 자식 Component로 데이터가 binding 될 때 혹은 변경되었을 때 호출됩니다. 따라서 @Input을 사용하지 않으면 호출되지 않습니다.

정확하게는 부모 Component로부터 자식 Component에게 전달하는 primitive 값이 변경되거나 혹은 참조하는 객체의 reference가 변경되어야 호출됩니다. 즉, 참조하는 객체의 property가 변경되는 경우에는 ngOnChanges가 호출되지 않는다는 것 기억하셔야 합니다.

@Input을 이용한 값의 binding은 생성자가 호출된 후에 일어납니다. 즉, 생성자에서 @Input을 이용해 binding한 값을 출력하면 undefined가 출력되게 됩니다. 간단한 이벤트 처리를 통해 @Input으로 전달되는 값을 변경해보면 값이 변경될 때마다 ngOnChanges hook method가 호출되는걸 확인할 수 있습니다.

ngOnChanges hook method의 인자로 SimpleChanges 객체를 하나 받을 수 있습니다. 해당 객체를 이용하면 변경되기 이전값과 이후값등을 알 수 있습니다.

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

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit, OnChanges {

  @Input() myInput: string;

  constructor() {
    console.log(`Constructor 호출!! => myInput : ${this.myInput}`);
  }

  ngOnChanges(simpleChanges: SimpleChanges) {
    console.log(`ngOnChanges 호출!! => myInput : ${this.myInput}`);
    console.log(simpleChanges.myInput.previousValue);
    console.log(simpleChanges.myInput.currentValue);
  }

  ngOnInit() {
  }

}

ngOnInit

ngOnInit는 ngOnChanges가 호출된 이후에 모든 속성에 대한 초기화가 완료된 시점에 딱 한번만 호출됩니다. 즉, class가 가지고 있는 속성과 @Input을 통해 값을 내려받은 속성이 모두 초기화가 된 이후에 호출됩니다. 결국 Component의 속성 참조는 ngOnInit hook method이후에 참조하는 것이 좋습니다.

결국 생성자는 Service의 Injection같은 사항을 처리하고 속성에 대한 초기화는 ngOnInit에서 처리하시는게 좋다는 말입니다.

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

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit, OnChanges {

  @Input() myInput: string;
  myString = 'Hello';

  constructor() {
    console.log(`Constructor 호출!! => myInput : ${this.myInput}`);
    console.log(`Constructor 호출!! => myString : ${this.myString}`);
  }

  ngOnChanges(simpleChanges: SimpleChanges) {
    console.log(`ngOnChanges 호출!! => myInput : ${this.myInput}`);
    console.log(simpleChanges.myInput.previousValue);
    console.log(simpleChanges.myInput.currentValue);
  }

  ngOnInit() {
    console.log(`ngOnInit 호출!! => myInput : ${this.myInput}`);
    console.log(`ngOnInit 호출!! => myString : ${this.myString}`);
  }

}

ngDoCheck

ngOnInit hook method가 호출된 이후에 호출됩니다. Component에서 발생하는 모든 상태변화에 반응하여 호출되어지는 hook method로 Angular의 Changes Detection이 상태변화를 감지하면 자동으로 호출되게 됩니다. 한가지 주의하셔야 할 점은 ngOnChanges와는 다르게 primitive값의 변경, reference 객체의 변경, reference객체의 속성변경에 대한 모든 변경에 대해 해당 hook mehtod가 호출된다는 점입니다. 심지에 이전값과 같은 값이 assign되었음에도 호출됩니다. 따라서 ngDoCheck을 많이 사용하게 되면 그만큼 성능이 저하될 수 있습니다.

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

interface IBook {
  btitle: string;
  bauthor: string;
}

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit, OnChanges, DoCheck {

  @Input() myInput: IBook;
  myString = 'Hello';

  constructor() {
    console.log(`Constructor 호출!! => myInput : ${this.myInput}`);
    console.log(`Constructor 호출!! => myString : ${this.myString}`);
  }

  ngOnChanges(simpleChanges: SimpleChanges) {
    console.log(`ngOnChanges 호출!! => myInput : ${this.myInput}`);
    console.log(simpleChanges.myInput.previousValue);
    console.log(simpleChanges.myInput.currentValue);
  }

  ngOnInit() {
    console.log(`ngOnInit 호출!! => myInput : ${this.myInput}`);
    console.log(`ngOnInit 호출!! => myString : ${this.myString}`);
  }

  ngDoCheck() {
    console.log(`ngDoCheck 호출!! => myInput : ${this.myInput}`);
    console.log(`ngDoCheck 호출!! => myString : ${this.myString}`);
  }
}

ngAfterContentInit, ngAfterContentChecked

최초의 ngDoCheck hook method가 호출된 후에 한번만 호출되며 앞서 배운 ngContent directive를 이용해 부모 Component의 template 일부를 자식 Component에서 projection한 후 호출됩니다. 여기서 Content의 의미는 ngContent directive처럼 외부에서 Component View안으로 내용을 가져온 것을 지칭합니다. 이 hook method 이후에 Change Detection이 실행된 후 바로 따라서 ngAfterContentChecked hook method가 호출됩니다.


ngAfterViewInit, ngAfterViewChecked

Component에 속한 모든 View와 ViewChild가 시작되고 나서 호출됩니다. 쉽게 생각하면 HTML이 모두 화면에 출력된 후 호출된다고 생각 하시면 됩니다. ngAfterViewChecked는 Component의 View에 대한 Change Detection이 실행되고 난 후 호출됩니다.


ngOnDestroy

Component가 소멸하기 직전에 호출됩니다. 일반적으로 사용된 자원에 대한 해제 코드가 들어옵니다.

import {
  AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, 
  Component, DoCheck, Input, OnChanges, OnDestroy, OnInit,
  SimpleChanges
} from '@angular/core';

interface IBook {
  btitle: string;
  bauthor: string;
}

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit, OnChanges, DoCheck, AfterContentInit,
  AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy {

  @Input() myInput: IBook;
  myString = 'Hello';

  constructor() {
    console.log(`Constructor 호출!! => myInput : ${this.myInput}`);
    console.log(`Constructor 호출!! => myString : ${this.myString}`);
  }

  ngOnChanges(simpleChanges: SimpleChanges) {
    console.log(`ngOnChanges 호출!! => myInput : ${this.myInput}`);
    console.log(simpleChanges.myInput.previousValue);
    console.log(simpleChanges.myInput.currentValue);
  }

  ngOnInit() {
    console.log(`ngOnInit 호출!! => myInput : ${this.myInput}`);
    console.log(`ngOnInit 호출!! => myString : ${this.myString}`);
  }

  ngDoCheck() {
    console.log(`ngDoCheck 호출!! => myInput : ${this.myInput}`);
    console.log(`ngDoCheck 호출!! => myString : ${this.myString}`);
  }

  ngAfterContentInit() {
    console.log(`ngAfterContentInit 호출!!`);
  }

  ngAfterContentChecked() {
    console.log(`ngAfterContentChecked 호출!!`);
  }

  ngAfterViewInit() {
    console.log(`ngAfterViewInit 호출!!`);
  }

  ngAfterViewChecked() {
    console.log(`ngAfterViewChecked 호출!!`);
  }

  ngOnDestroy() {
    console.log(`ngOnDestroy 호출!!`);
  }
}

이번 포스트는 Angular가 제어하는 Component와 Directive의 lifecycle에 대해서 살펴봤습니다. 어떤 시점에 어떤 hook method가 호출되는지 이해하고 Change Detection이 어느 시점에 호출되는지를 이해하시면 조금 더 Angular를 이해하는데 도움이 될 듯 합니다.

End.


Angular 강좌는 아래의 책과 사이트를 참조했습니다. 조금 더 자세한 사항을 알고 싶으시면 해당 사이트를 방문하세요!!