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 구독 해제와 같은 작업을 많이 합니다.
생명주기가 많지만 각자의 목적이 뚜렷하네요! 앞으로 적절한 상황에 활용할 수 있다면 좋을 것 같습니다 :)