티스토리 뷰

반응형
SMALL


Angular 생명주기(Lifecycle)와 훅(Hook) 메소드


Angular의 생명주기(Life cycle)와 훅(Hook) 메소드에 대해 알아보겠습니다.


1. 생명주기


Angular의 컴포넌트와 디렉티브는 생명주기(Lifecycle)를 갖는데, 생명주기는 Angular가 컴포넌트와 디렉티브를 생성하여 소멸하기까지의 과정을 관리하는 것을 의미합니다. 이와 관련하여 생명주기 이름 앞에 ng가 붙은 훅(Hook) 메소드를 제공하는데 이를 구현하여 생명주기의 각 단계에서 처리해야하는 내용을 정의할 수 있습니다.


Angular는 다음의 순서대로 생명주기를 관리합니다.



※ 디렉티브 생명주기 훅 메소드

디렉티브도 컴포넌트와 동일한 생명주기 훅 메소드를 사용합니다. 하지만 디렉티브에는 뷰가 없기 때문에 뷰와 관련된 생명주기인 ngAfterViewInit, ngAfterViewChecked, ngAfterContentInit, ngAfterContentChecked 메소드는 디렉티브에 존재하지 않습니다.


2. 생명주기 훅 메소드


생명주기 훅(Hook) 메소드는 인터페이스의 형태로 제공됩니다. 모든 생명주기 훅 메소드를 구현할 필요는 없으며, 특정 생명주기에 대해 구현해야할 기능이 있을 때 필요한 해당 훅 메소드를 구현하면 됩니다. 각각의 생명주기 훅 메소드에 대해 알아보겠습니다.


  • ngOnChanges

부모 컴포넌트에서 자식 컴포넌트의 입력 프로퍼티(@Input 데코레이터가 적용된 프로퍼티)에 바인딩한 값이 초기화 또는 변경되었을 때 호출됩니다. ngOnInit 호출 이전에 최소 1회 호출되며 이후 입력 프로퍼티가 변경될 때마다 호출됩니다. ngOnChanges 메소드는 입력 프로퍼티의 정보를 담고 있는 SimpleChanges 객체를 파라미터로 전달 받을 수 있습니다. 이 객체는 입력 프로퍼티의 현재값(currentValue)과 이전값(previousValue)을 포함하고 있습니다.


  • ngOnInit

ngOnChanges 메소드 동작 이후 입력 프로퍼티를 포함한 모든 프로퍼티의 초기화가 완료된 시점에 한 번만 호출됩니다. 일반적으로 프로퍼티의 초기화는 TypeScript에서는 constructor에서 하는 것이 일반적이지만 Angular에서는 ngOnInit에서 수행하는 것이 좋습니다. 


※ constructor와 ngOnInit에서의 프로퍼티 초기화

TypeScript의 constructor가 실행되는 시점에 Angular에서 관리하는 입력 프로퍼티는 초기화되기 이전의 상태입니다. 따라서 이 시점에 입력 프로퍼티를 참조할 경우 undefined가 반환되어 의도하지 않은 결과가 발생할 수 있습니다. 

ngOnInit은 입력 프로퍼티의 참조가 보장되기 때문에 프로퍼티 초기화와 서버에서 데이터를 조회하여 할당하는 것과 같은 동작은 ngOnInit에서 수행해주는 것이 좋습니다.


  • ngDoCheck

ngOnInit 메소드 동작 이후 컴포넌트나 디렉티브의 상태 변화가 발생할 때마다 호출됩니다. 즉, 변화 감지(change detection) 로직이 실행될 때 호출되는데 컴포넌트의 프로퍼티 값이 변경되거나 DOM 이벤트, Timer 함수의 tick 이벤트, Ajax 통신과 같은 비동기 처리 등이 수행되는 경우가 이에 해당합니다. 하지만 ngDoCheck 메소드는 모든 상태 변화가 발생할 때마다 매번 호출되어 성능에 영향을 줄 수 있기 때문에 이를 고려하여 사용해야 합니다. 


※ ngOnChanges와 ngDoCheck의 차이

ngOnChanges는 입력 프로퍼티의 값에 변화에 따라 호출되지만, ngDoCheck은 모든 상태 변화에 따라 호출됩니다.


  • ngAfterContentInit

ngContent 디렉티브를 사용하여 자식 컴포넌트에 부모 컴포넌트의 템플릿 조각을 전달(content projection)한 이후 호출됩니다. ngDoCheck 메소드 호출 이후에 한 번만 호출되며 해당 컴포넌트에서만 동작합니다.


  • ngAfterContentChecked

부모 컴포넌트가 전달한 템플릿 조각을 체크한 후 ngAfterContentInit 메소드 호출 이후에 호출됩니다. ngAfterContentInit 메소드와 마찬가지로 해당 컴포넌트에서만 동작합니다.


  • ngAfterViewInit

컴포넌트의 View와 ViewChild가 초기화된 이후 호출됩니다. HTML에 작성된 내용이 화면에 모두 출력되고나서 호출되며 해당 컴포넌트에서만 동작합니다.


  • ngAfterViewChecked

컴포넌트의 View와 ViewChiled를 체크한 이후 호출됩니다. ngAfterViewInit 메소드 호출 이후에 호출되며 컴포넌트의 View에 대한 변화 감지(change detection)가 이루어질 때 동작합니다. ngAfterViewInit 메소드와 마찬가지로 해당 컴포넌트에서만 동작합니다.


  • ngOnDestroy

컴포넌트나 디렉티브가 소멸하기 이전에 호출됩니다. RxJS의 unsubscribe() 메소드와 같이 메모리 누수 등을 방지하기 위한 내용들을 정의합니다.


다음으로 아래의 코드를 통해 생명주기 훅 메소드의 동작에 대해 알아보겠습니다. 생성한 Angular 애플리케이션의 src > app 아래에 다음과 같이 파일을 구성해줍니다.


- app.module.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* app.module.ts */
 
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
 
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ChildComponent } from './child.component';
 
@NgModule({
  declarations: [
    AppComponent,
    ChildComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}
cs


- lifecycle.component.html

1
2
3
4
5
6
7
8
9
10
<!-- lifecycle.component.html -->
 
<button (click)="changeStatus()">
  {{status ? 'Destroy Child' : 'Create Child'}}
</button>
<div *ngIf="status">
  <button (click)="immutable='HELLO'">Change primitive type property</button>
  <button (click)="mutable.name='kim'">Change object property</button>
</div>
<app-child [immutable]="immutable" [mutable]="mutable"></app-child>
cs


- app.component.ts

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
/* app.component.ts */
 
import { Component } from '@angular/core';
 
@Component({
  selector: 'app-root',
  templateUrl: './lifecycle.component.html'
})
export class AppComponent {
 
  public status: boolean = false;
 
  public immutable: string = 'Hello';
  public mutable: object = {
    name'Lee'
  };
 
  constructor() {
    console.log('[parent constructor]');
  }
 
  public changeStatus() {
    this.status = !this.status;
  }
}
cs


- child.component.html

1
2
3
4
5
<!-- child.component.html -->
 
<p>Child Component</p>
<p>Property from Parent Component: {{immutable}}</p>
<p>Property from Parent Component: {{mutable|json}}</p>
cs


- child.component.ts

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/* child.component.ts */
 
import { Component, Input, OnChanges, OnInit, DoCheck, OnDestroy, SimpleChanges,
  AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked } from '@angular/core';
 
@Component({
  selector: 'app-child',
  templateUrl: './child.component.html'
})
export class ChildComponent implements OnChanges, OnInit, DoCheck, OnDestroy,
  AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked {
 
  @Input() public immutable: string;
  @Input() public mutable: object;
 
  public prop: string = 'normal prop';
 
  constructor() {
    console.log('[child constructor]');
  }
 
  ngOnChanges(changes: SimpleChanges) {
    console.log('[ngOnChanges]');
    console.log('changes: ', changes);
    console.log('immutable: ', this.immutable);
    console.log('mutable: ', this.mutable);
  }
 
  ngOnInit() {
    console.log('[ngOnInit]');
    console.log('prop: ', this.prop);
    console.log('immutable: ', this.immutable);
    console.log('mutable: ', this.mutable);
  }
 
  ngDoCheck() {
    console.log('[ngDoCheck]');
    console.log('immutable: ', this.immutable);
    console.log('mutable: ', this.mutable);
  }
 
  ngAfterContentInit() {
    console.log('[ngAfterContentInit]');
  }
 
  ngAfterContentChecked() {
    console.log('[ngAfterContentChecked]');
  }
 
  ngAfterViewInit() {
    console.log('[ngAfterViewInit]');
  }
 
  ngAfterViewChecked() {
    console.log('[ngAfterViewChecked]');
  }
 
  ngOnDestroy() {
    console.log('[ngOnDestroy]');
  }
 
}
cs


위와 같이 각각의 파일을 구성한 후 실행하면 브라우저와 콘솔에 다음과 같이 출력됩니다.



[Create Child] 버튼을 클릭한 경우 콘솔에 다음과 같이 출력됩니다. 

상태 변화가 일어났지만 입력 프로퍼티에는 변화가 없기 때문에 ngDoCheck 메소드만 호출된 것을 확인할 수 있습니다.



[Change primitive type property] / [Change object property] 버튼을 클릭한 경우 콘솔에 다음과 같이 출력됩니다.

[Change primitive type property] 버튼을 클릭했을 경우 부모 컴포넌트의 immutable 프로퍼티의 값이 변경되어 자식 컴포넌트의 입력 프로퍼티로 전달됩니다. 이 때 입력 프로퍼티에 대해 재할당이 이루어지기 때문에 ngOnChanges 메소드가 호출된 것을 확인할 수 있습니다.



[Change object property] 버튼을 클릭한 경우 부모 컴포넌트에서 mutable 객체안의 프로퍼티를 변경하여 자식 컴포넌트에 전달합니다. 이 때 참조하는 객체 자체는 변경된 것이 없으므로 입력 프로퍼티가 변경되지 않은 것으로 간주되고, 따라서 ngOnChanges 메소드가 호출되지 않은 것을 확인할 수 있습니다.



이상으로 Angular 생명주기(Lifecycle)와 훅(Hook) 메소드에 대해 알아봤습니다.


※ 참고 문헌



반응형
LIST
댓글
댓글쓰기 폼