본문 바로가기

카테고리 없음

Angular Component LifeCycle (2) - ngOnChanges(), ngDoCheck(), ngAfterContentInit(), ngAfterContentChecked()

 

 

Angular Component 생명주기 2탄으로 돌아왔습니다! 🤪

 

5. ngOnChanges(changes: SimpleChanges)

ngOnChanges는 component의 inuput이 변경되었을 때 호출되는 생명주기 hook 입니다.

인자로 들어오는 `SimpleChanges` 타입에는 무엇이 있을까요?!

 

export declare interface SimpleChanges {
    [propName: string]: SimpleChange;
}

export declare class SimpleChange {
    previousValue: any;
    currentValue: any;
    firstChange: boolean;
    constructor(previousValue: any, currentValue: any, firstChange: boolean);
    /**
     * Check whether the new value is the first value assigned.
     */
    isFirstChange(): boolean;
}

 

위와 같이 `SimpleChanges` 안에는 input으로 들어오는 각각의 `propName`을 key로 하는 객체가 들어옵니다.

value에는 input의 이전값, 현재값, 해당 value가 처음으로 변경되었는지 여부를 알 수 있습니다.

 

+) `ngOnChanges()`는 초기화 시에는 `ngOnInit()`보다 먼저 일어납니다. 그래서 `ngOnInit()`이 Input값이 초기화 된 후 접근할 수 있는 상태가 됩니다.

 

6. ngDoCheck()

`ngDoCheck`는 Angular가 컴포넌트 변경사항을 확인할때마다 일어납니다. 모든 변경감지 주기에서 실행되기 때문에 성능에 영향을 미칠 수 있습니다. `ngOnChanges`는 input이 변경될 때에만 호출되지만 `ngDoCheck`는 모든 변경 감지 주기에서 실행됩니다.

import { Component } from '@angular/core';

@Component({
  selector: 'app-lifecycle',
  template: `
    <h1>Lifecycle</h1>
    <button (click)="handleClick()">Increment</button>
    <p>current: {{ count }}</p>
  `,
  standalone: true,
})
export class LifecycleComponent {
  count = 0;
  prevCount = 0;

  constructor() {}

  ngDoCheck() {
    if (this.count !== this.prevCount) {
      console.log('count 변경됨', this.prevCount, ' -> ', this.count);
      this.prevCount = this.count;
    }
  }

  handleClick() {
    this.count = this.count + 1;
  }
}

 

ngDoCheck를 쓰지 않아도 충분히 구현할 수 있는 코드이지만 억지로 만들어봤습니다..

 

버튼을 클릭할 때마다 변경 감지가 일어나고, `ngDoCheck`가 실행되는 모습을 볼 수 있습니다.

 

 

7. ngAfterContentInit(), ngAfterContentCheck()

부모 컴포넌트가 삽입한 `<ng-content></ng-content>`의 변경을 감지하고 호출됩니다.

 

`ngAfterContentInit()`은 `<ng-content>`가 초기화 된 후 한 번만 실행됩니다. 

`ngAfterContentCheck()`는 `<ng-content>`가 변경이 될 때마다 실행됩니다.

 

import { NgIf } from '@angular/common';
import {
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  ContentChildren,
  ElementRef,
  HostListener,
  Input,
  QueryList,
} from '@angular/core';

@Component({
  selector: 'app-dropdown',
  template: `<div class="dropdown">
    <div>항목 개수: {{ itemCount }}</div>
    <button (click)="toggleDropdown()">{{ title }}</button>
    <div class="dropdown-menu" *ngIf="isOpen">
      <ng-content></ng-content>
    </div>
  </div> `,
  styleUrls: ['dropdown.component.css'],
  imports: [NgIf],
  standalone: true,
})
export class DropdownComponent {
  @Input() title: string = '';
  @ContentChildren('dropdownItem') dropdownItems!: QueryList<ElementRef>;

  isOpen: boolean = false;
  itemCount: number = 0;

  ngAfterContentInit() {
    this.itemCount = this.dropdownItems.length;
  }

  toggleDropdown() {
    this.isOpen = !this.isOpen;
  }
  closeDropdown(event: Event) {
    const target = event.target as HTMLElement;
    if (!target.closest('.dropdown')) {
      this.isOpen = false;
    }
  }
}

@Component({
  selector: 'app-lifecycle',
  template: `
    <h1>Lifecycle</h1>
    <app-dropdown title="드롭다운">
      <button #dropdownItem (click)="action1()">항목 1</button>
      <button #dropdownItem (click)="action2()">항목 2</button>
      <button #dropdownItem (click)="action3()">항목 3</button>
    </app-dropdown>
  `,
  imports: [DropdownComponent],
  standalone: true,
})
export class LifecycleComponent {
  constructor() {}
  action1() {
    console.log('action1');
  }
  action2() {
    console.log('action2');
  }
  action3() {
    console.log('action3');
  }
}

 

위 코드는 `ng-content`를 이용해 드롭다운 메뉴를 만든 에시입니다. 드롭다운 컴포넌트에서 `ng-content`에 들어오는 항목의 개수를 얻기 위해서는 `ngAfterContentInit()`을 이용해 content에 접근할 수 있습니다.

결과 화면은 위와 같습니다. 항목 개수를 잘 가져왔네요 😆

 

결론

8가지의 Angular Life Cycle에 대해 알아보았습니다.

Angular Life Cycle의 전체적인 순서는 아래와 같습니다.

출처: https://angular.dev/guide/components/lifecycle#during-initialization

 

 

각각의 목적을 다시 한 번 간단히 살펴보자면,

 

`ngOnInit()`은  컴포넌트가 생성된 후 초기화 작업을 할 때 사용합니다. http 요청이나 옵저버블 구독과 같은 작업을 할 수 있습니다.

 

`ngOnChanges`는 Input 값이 변경될 때 실행됩니다. input을 사용하기 전에 특정한 작업을 하고 싶다면 ngOnChanges를 사용할 수 있습니다.

`ngDoCheck`는 Input 속성 뿐만 아니라 내부 프로퍼티가 변경될 때에도 호출됩니다. 성능에 안 좋은 영향이 있을 수 있으니 복잡한 연산은 하지 않는 것이 좋습니다.

 

`ngAfterViewInit()`과 `ngAfterViewChecked()`는 View가 렌더링 된 이후에 실행됩니다. 컴포넌트의 뷰가 완전히 로드된 후 해야하는 작업을 수행하기 좋습니다. nativeElement의 값을 가져올 때도 이 생명주기 함수를 사용할 수 있습니다.

 

`ngAfterContentInit()`과 `ngAfterContentChecked()`는 ng-content가 초기화 된 후, 변경이 된 후에 호출되는 생명주기 메서드입니다.

 

`ngOnDestroy()`는 컴포넌트가 사라질 때 호출되는 생명주기 입니다. 주로 observable 구독 해제와 같은 작업을 많이 합니다.

 

생명주기가 많지만 각자의 목적이 뚜렷하네요! 앞으로 적절한 상황에 활용할 수 있다면 좋을 것 같습니다 :)