본문 바로가기

Programming/React, Angular, GraphQL

Angular Component Lifecycle

컴포넌트의 lifecycle은 컴포넌트의 생성부터 제거되기까지 발생하는 일련의 단계를 뜻한다. 각 단계는 Angular의 컴포넌트 렌더링의 각 부분들을 나타내며 시간에 따른 업데이트를 확인한다. Angular는 애플리케이션 트리를 top에서 bottom 경로로 돌면서 변경되어야 할 템플릿 바인딩이 있는지 확인한다. Angular가 이런 트리 순회를 하는 동안 lifecycle hook이 실행된다. 이 트리 순회는 각 컴포넌트를 1번씩 방문하기때문에 프로세스 중간에 state 변경이 일어나지않도록 대비해야한다.

 

이 글은 Angular 공식문서의 LifeCycle 항목을 읽고 번역해본 내용이다. 참고로 Version 17 기준이다.

공식 문서 바로보기: https://angular.dev/guide/components/lifecycle

 

총 4단계가 있다. Creation Change Detection Rendering Destruction

  1. Creation
    • constructor: 기본 Javascript 클래스 constructor이다. Angular가 컴포넌트를 초기화할 때 실행된다.
  2. Change Detection
    • ngOnInit: 컴포넌트 input들을 모두 초기화 한 이후에 1번 실행된다.
    • ngOnChanges: input이 변경될 때마다 실행된다.
    • ngDoCheck: 변경사항을 확인할 때마다 실행된다.
    • ngAfterViewInit: 컴포넌트 view가 초기화된 이후에 1번 실행된다.
    • ngAfterContentInit: 컴포넌트 content가 초기화 된 이후에 1번 실행된다.
    • ngAfterViewChecked: 컴포넌트 view의 변경사항을 확인할 때마다 실행된다.
    • ngAfterContentChecked: 컴포넌트 component의 변경사항을 확인할 때마다 실행된다.
  3. Rendering
    • afterNextRender: DOM에 모든 컴포넌트가 렌더링 된 이후에 1번 실행된다.
    • afterRender: DOM에 모든 컴포넌트가 렌더링 된 이후마다 실행된다.
  4. Destruction
    • ngOnDestroy: 컴포넌트가 제거되기 이전에 1회 실행된다.

각 lifecycle에 대해 더 자세하게 알아보자.

ngOnInit

Angular가 모든 컴포넌트의 input을 초기 값으로 초기화 한 이후에 실행된다. 딱 1번 실행된다. 컴포넌트 각자 고유의 템플릿이 초기화되기전에 실행된다. 즉, 컴포넌트의 상태를 초기 input 값으로 업데이트할 수 있다는 뜻이다.

ngOnChanges

컴포넌트 input이 변경될 때마다 실행된다. 컴포넌트의 초기화 input 값으로 컴포넌트의 상태를 업데이트할 수 있다. 초기화 당시, 첫번째 ngOnChanges는 ngOnInit 이전에 불린다.

변경사항 확인하기

ngOnChanges 메소드는 SimpleChanges 인자값 1개를 받는다. SimpleChange는 input의 직전 값, 현재 값, input이 처음으로 변경되었는지 여부에 대한 플래그값을 포함하고 있다.

@Component({
  /* ... */
})
export class UserProfile {
  @Input() name: string = '';
  ngOnChanges(changes: SimpleChanges) {
    for (const inputName in changes) {
      const inputValues = changes[inputName];
      console.log(`Previous ${inputName} == ${inputValues.previousValue}`);
      console.log(`Current ${inputName} == ${inputValues.currentValue}`);
      console.log(`Is first ${inputName} change == ${inputValues.firstChange}`);
    }
  }
}

ngOnDestroy

컴포넌트가 제거되기 전에 1회 실행된다. 더이상 화면에 보이지 않을때 Angular는 컴포넌트를 제거한다. 예를 들면, NgIf로 인해 숨겨지거나, 다른 페이지로 navigating 되는 경우이다.

DestroyRef

ngOnDestroy 메소드의 대체제로, DestroyRef 인스턴스를 주입시킬 수 있다. DestroyRef.onDestroy() 호출하여 컴포넌트가 파괴 시 콜백을 등록할 수 있다.

@Component({
  /* ... */
})
export class UserProfile {
  constructor(private destroyRef: DestroyRef) {
    destroyRef.onDestroy(() => {
      console.log('UserProfile destruction');
    });
  }
}

컴포넌트 바깥에서 DestroyRef 인스턴스를 넘겨줄 수도 있다. 컴포넌트가 파괴될 때 cleanup 동작을 해야되는 코드가 있다면, 이 패턴을 사용해봐라. ngOnDestroy 메소드에 cleanup 코드를 모두 넣어두는 것보단 DestroyRef를 사용해서 적절히 분산하는 것도 좋다.

ngDoCheck

Angular가 변경사항을 확인하기 직전에 매번 실행된다. Angular의 일반적인 변경사항 감지를 하거나, 수동으로 상태 변경을 확인하고 수동으로 컴포넌트의 상태를 업데이트할 때 이 lifecycle hook을 사용하면 된다.

이 메소드는 매우 자주 실행되므로 페이지의 퍼포먼스에 악영향을 끼칠 수 있다. 따라서 대안이 없을 때에만 사용하는 것이 좋다.

초기화 작업이 진행되는 동안, 첫번째 ngDoCheck은 ngOnInit 이후에 실행된다.

ngAfterViewInit

컴포넌트 템플릿의 모든 자식들이 초기화되면 1번 실행된다. View queries의 결과를 확인할 때 사용하면 된다.

https://angular.dev/guide/components/queries#view-queries

ngAfterContentInit

컴포넌트의 중첩된 모든 자식들이 초기화되면 1번 실행된다. Content queries의 결과를 확인할 때 사용하면 된다.

ngAfterViewChecked

컴포넌트 템플릿의 모든 자식들의 변경사항을 체크할 때마다 호출된다.

이 메소드는 매우 자주 실행되므로 페이지의 퍼포먼스에 악영향을 끼칠 수 있다. 따라서 대안이 없을 때에만 사용하는 것이 좋다.View queries의 변경된 상태에 접근하고 싶을 때 사용하면 된다.

ngAfterContentChecked

컴포넌트의 중첩된 모든 자식들의 변경사항을 체크할 때마다 호출된다.

이 메소드는 매우 자주 실행되므로 페이지의 퍼포먼스에 악영향을 끼칠 수 있다. 따라서 대안이 없을 때에만 사용하는 것이 좋다. Content queries의 변경된 상태에 접근하고 싶을 때 사용하면 된다.

afterRender와 afterNextRender

Angular가 DOM에 모든 컴포넌트의 페이지 렌더링을 마쳤을 때, 호출될 수 있도록 하는 render 콜백함수를 등록할 수 있게 해준다. 다른 메소드들은 class 메소드인데, 이 두 개의 함수는 콜백을 허용하는 standalone 함수 형태이다. Render 콜백함수는 서버사이드 렌더링이나, 빌드 타임 동안이나, pre-rendering 동안에는 실행되지 않는다.

afterRender phases

afterRender나 afterNextREnder를 사용하면 phase를 정의할 수 있다. DOM 작업의 순서를 제어하여 레이아웃 충돌을 최소화하기위해 쓰기 작업을 읽기 작업 앞에 순차적으로 배치할 수 있다.

import {Component, ElementRef, afterNextRender, AfterRenderPhase} from '@angular/core';
@Component({...})
export class UserProfile {
  private elementHeight = 0;
  constructor(elementRef: ElementRef) {
    const nativeElement = elementRef.nativeElement;
    afterNextRender(() => {
      nativeElement.style.padding = computePadding();
    }, {phase: AfterRenderPhase.Write});
    // 모든 write이 실행된 이후에 `Read` phase를 써라
    afterNextRender(() => {
      this.elementHeight = nativeElement.getBoundingClientRect().height;
    }, {phase: AfterRenderPhase.Read});
  }
}

phase는 총 4단계가 있다.

  • Earlyread: 차후 계산에 엄격하게 필요한 레이아웃 영향을 주는 DOM 속성과 스타일을 읽는다. 가능한 Write, Read phase를 사용하고 이 단계는 피하는게 좋다.
  • MixedReadWrite: 기본 phase이다. read, write 모두 레이아웃에 영향을 주는 속성과 스타일에 필요하다. 이왕이면 Write나 Read phase를 사용해라.
  • Write: 레이아웃 영향을 주는 DOM 속성과 스타일을 작성할 때 사용한다.
  • Read: 레이아웃 영향을 주는 DOM 속성과 스타일을 읽을 때 사용한다.

실행 순서

초기화작업 동안

 

후속 업데이트 작업동안

사실 두 개의 차이를 보자면, 순서는 같고 초기화 작업동안에만 ngOnInit, ngAfterContentInit, ngAfterViewInit 세 개가 추가되어있다.

 

이렇게 Angular의 컴포넌트 lifecycle에 대해 알아봤다. lifecycle은 매우 중요하며, 각 단계마다 어떤 작업을 하는지 알고있으면, 더욱 더 견고하고 최적화된 컴포넌트를 개발할 수 있다.

반응형