본문 바로가기

Frontend

Angular Component LifeCycle (1) - ngOnInit, ngOnDestroy, ngAfterViewInit, ngAfterViewChecked

요즘 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의 HttpClientEventEmitter는 자동으로 구독을 해제합니다.

//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의 라이프사이클 메서드들에 대해 알아보겠습니다!