요즘 Angular를 사용하는데, Angular 컴포넌트의 생명주기에 대해 잘 모르고 있다는 생각이 들어 생명주기에 대해 공부해보고자 합니다. 😊
Angular 컴포넌트의 Life Cycle은 총 8가지가 있습니다!
가장 자주 쓰이는 ngOnInit()
과 ngOnDestroy()
그리고 다소 덜 쓰이는 ngOnChanges()
, ngDoCheck()
, ngAfterContentInit()
, ngAfterContentChecked()
, ngAfterViewInit()
, ngAfterViewChecked()
까지 총 8개가 있네요.
너무 많아서 우선 ngOnInit()
과 ngOnDestroy()
, ngAfterViewInit()
, ngAfterViewChecked()
4가지를 보려합니다.
1. ngOnInit()
이름에서부터 알 수 있듯이 컴포넌트가 생성된 직후 초기화 작업이 가능합니다.
클래스의 Constructor 이후에 실행됩니다.
그렇다면 왜 Constructor 내에 초기화 하는 코드를 때려넣으면 되지 ngOnInit()이 따로 있을까요? 🤷🏿
import { Component, Input, OnInit } from '@angular/core';
@Component({
selector: 'app-lifecycle-child',
template: ` <div>{{ data }}</div> `,
standalone: true,
})
export class LifecycleChildComponent implements OnInit {
@Input() data: string;
constructor() {
this.data = this.data + '님';
//[Error] TS2565: Property 'data' is used before being assigned.
}
ngOnInit() {
this.data = this.data + '님';
//🙆
}
}
Constructor는 Javascript에서 제공해주는 클래스의 기능입니다. 이때 아직 @Input
의 값은 설정되지 않은채로 undefined
상태입니다. 그래서 constructor에서 data
를 접근할 때 오류가 뜨게 됩니다!
이외에도 Angular 문서에 따르면
Components should be cheap and safe to construct. You should not, for example, fetch data in a component constructor. You shouldn't worry that a new component will try to contact a remote server when created under test or before you decide to display it.
An ngOnInit() is a good place for a component to fetch its initial data.
constructor에서는 간단한 연산을 하길 권장하네요. constructor에서는 의존성 주입 및 객체 생성과 관련된 간단한 작업들을 해야합니다.
2. ngOnDestroy()
컴포넌트가 DOM에서 제거될 때 호출되는 메서드입니다.
RxJS의 observable
을 구독했을 때에도, ngOnDestroy
에서 구독해제를 해줄 수 있습니다.
참고로 Angular의 HttpClient
나 EventEmitter
는 자동으로 구독을 해제합니다.
//in Component
private onDestroy$ = new Subject<void>();
private o$ = new Observable();
ngOnInit() {
this.o$.pipe(takeUntil(onDestory$)).subscribe(...);
}
ngOnDestroy() {
this.onDestroy$.next();
this.onDestroy$.complete();
}
위 코드는 옵저버블 구독 해제 예시입니다.
o$
의 pipe에서 takeUntil을 통해 onDestroy$
가 값을 방출할 때까지 구독을 유지하게 됩니다.
ngOnDestroy() 내의 this.onDestroy$.next();
에서 값을 방출하면 o$
는 자동으로 구독이 해제되고 onDestroy$
자체도 complete를 통해 해제됩니다.
3. ngAfterViewInit()
ngAfterViewInit()은 Angular가 뷰를 렌더링한 이후에 호출됩니다.
1. DOM에 접근하거나,
2. 자식 컴포넌트에 접근할 때,
ngAfterViewInit() 내에서 안전하게 접근가능합니다.
@Component({
selector: 'app-lifecycle',
template: `
<h1>Lifecycle</h1>
<app-lifecycle-child></app-lifecycle-child>
<div #myDOM>언제부터 DOM에 접근할 수 있을까요?</div>
`,
standalone: true,
imports: [LifecycleChildComponent],
})
export class LifecycleComponent implements AfterViewInit {
@ViewChild(LifecycleChildComponent) private child!: LifecycleChildComponent;
@ViewChild('myDOM') private myDOM!: ElementRef;
constructor() {}
ngOnInit() {
console.log('onInit: ', this.child);
console.log('DOM in onInit : ', this.myDOM?.nativeElement.textContent);
}
ngAfterViewInit(): void {
console.log('afterViewInit: ', this.child);
console.log('DOM in afterViewInit : ', this.myDOM.nativeElement.extContent);
}
}

위와 같이 각 life cycle에서 자식 컴포넌트와 DOM에 접근했을 때, onInit의 경우 undefined
가 나오는 것을 확인할 수 있습니다.
4. ngAfterViewChecked()
Angular에서 뷰가 변경된 뒤에 호출됩니다.
자주 호출될 수 있으므로 무거운 연산을 지양해야 합니다.

@Component({
selector: 'app-lifecycle',
template: `
<h1>Lifecycle</h1>
<button (click)="handleClick()">Increment</button>
<p>{{ count }}</p>
`,
standalone: true,
})
export class LifecycleComponent {
count = 0;
constructor() {}
ngAfterViewChecked() {
console.log('view changed: ', this.count);
}
handleClick() {
this.count = this.count + 1;
}
}
위 코드는 버튼을 클릭 시 count가 하나씩 올라갑니다. 버튼 클릭시 Angular는 Zone.js를 이용해 변경을 감지하고 view가 새롭게 렌더링됩니다.
이 때, ngAfterViewChecked
는 변경감지 주기가 끝날 때마다 호출되고 위 사진과 같이 count 값을 콘솔에 출력해주고 있습니다!
ngAfterViewInit
은 뷰가 렌더링된 첫번째 순간 (= 변경 감지 주기의 첫번째 cycle)에만 일어나지만 ngAfterViewChecked
는 매 변경 감지 주기가 끝날 때마다 일어나므로 가벼운 연산 위주로 해야합니다.
다음 글에서는 남은 4가지 Angular의 라이프사이클 메서드들에 대해 알아보겠습니다!