[Angular] ChangeDetectorRef: 수동 변경 감지에 대해 자세히 알아보기

변경 감지(Change Detection)는 Angular 애플리케이션의 중요한 측면으로, UI가 애플리케이션 상태와 동기화된 상태를 유지하도록 보장합니다. Angular는 효율적인 기본(default) 변경 감지 전략을 제공하지만 성능 최적화나 복잡한 사용 사례에는 보다 세부적인 접근 방식이 필요한 경우가 있습니다.

이 글에서는 ChangeDetectorRef의 강력한 기능을 심층적으로 살펴보고 해당 방법을 검토하고 잠재력을 최대한 보여주는 예를 보여 드리겠습니다.

ChangeDetectorRef 이해

ChangeDetectorRef는 변경 감지 메커니즘에 대한 직접적인 인터페이스를 제공하는 Angular 클래스입니다. 이를 통해 개발자는 변경 감지를 수동으로 트리거(trigger)하고, 변경 감지 트리에서 컴포넌트를 분리하거나 다시 연결하고, 향후 변경 감지를 위해 컴포넌트를 표시할 수 있습니다. ChangeDetectorRef를 사용하면 개발자는 변경 감지 프로세스를 더 효과적으로 제어할 수 있어 애플리케이션의 성능과 유연성이 향상됩니다.

ChangeDetectorRef가 제공하는 주요 메서드는 다음과 같습니다.

  • detectorChanges(): 현재 컴포넌트와 해당 하위 컴포넌트에 대한 변경 감지를 수동으로 트리거합니다.
  • markForCheck(): 다음 변경 감지 주기 동안 현재 컴포넌트와 해당 상위 컴포넌트에 변경 감지가 필요한 것으로 표시합니다.
  • detach(): 변경 감지 트리에서 현재 컴포넌트를 분리하여 이 컴포넌트와 해당 하위 컴포넌트에 대한 자동 변경 감지를 효과적으로 중지합니다.
  • reattach(): 현재 컴포넌트를 변경 감지 트리에 다시 연결하여 자동 변경 감지를 다시 한번 활성화합니다.

예제 1: 동적 컴포넌트 로딩 및 변경 감지

사용자 상호 작용에 따라 컴포넌트가 동적으로 로드되는 애플리케이션을 상상해 보세요. 이러한 시나리오에서는 ChangeDetectorRef를 사용하여 변경 감지를 최적화하고 필요한 경우에만 UI가 업데이트되도록 할 수 있습니다.

먼저 컴포넌트를 동적으로 로드하는 디렉티브을 만들어 보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { ComponentFactoryResolver, Directive, Input, OnInit, ViewContainerRef } from '@angular/core';

@Directive({
selector: '[appDynamicComponentLoader]',
})
export class DynamicComponentLoaderDirective implements OnInit {
@Input('appDynamicComponentLoader') component: any;

constructor(private viewContainerRef: ViewContainerRef, private componentFactoryResolver: ComponentFactoryResolver) {}

ngOnInit() {
const factory = this.componentFactoryResolver.resolveComponentFactory(this.component);
this.viewContainerRef.createComponent(factory);
}
}

이제 사용자 입력에 따라 동적으로 다양한 컴포넌트를 로드하는 컨테이너 컴포넌트를 만들어 보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { Component, ComponentRef, ViewChild } from '@angular/core';
import { DynamicComponentLoaderDirective } from './dynamic-component-loader.directive';

@Component({
selector: 'app-container',
template: `
<ng-container *appDynamicComponentLoader="selectedComponent"></ng-container>
<button (click)="loadComponent('A')">Load Component A</button>
<button (click)="loadComponent('B')">Load Component B</button>
`,
})
export class ContainerComponent {
@ViewChild(DynamicComponentLoaderDirective, { static: true }) dynamicComponentLoader: DynamicComponentLoaderDirective;

selectedComponent: any;

loadComponent(type: string) {
this.selectedComponent = type === 'A' ? ComponentA : ComponentB;
this.dynamicComponentLoader.detectChanges();
}
}

이 예에서는 새 컴포넌트가 로드될 때 변경 감지를 수동으로 트리거하기 위해 discoverChanges() 메서드를 사용하고 있습니다.

예제 2: Observable을 사용한 고급 변경 감지

다양한 컴포넌트가 Observable을 통해 데이터 업데이트를 받는 애플리케이션을 생각해 보세요. 우리는 특정 컴포넌트가 새 데이터를 수신할 때만 발생하도록 변경 감지를 최적화하려고 합니다.

먼저 데이터 서비스를 만듭니다.

1
2
3
4
5
6
7
8
9
10
11
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable({
providedIn: 'root',
})
export class DataService {
private dataSubject = new BehaviorSubject<any[]>([]);

data$ = this.dataSubject.asObservable();
}

이제 데이터 서비스를 사용하고 여러 하위 컴포넌트가 있는 상위 컴포넌트를 만들어 보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { Component, OnInit } from '@angular/core';
import { DataService } from './data.service';

@Component({
selector: 'app-parent',
template: `
<h2>Parent Component</h2>
<app-child *ngFor="let item of items; index as i" [data]="item" [index]="i"></app-child>
`,
})
export class ParentComponent implements OnInit {
items: any[] = [];

constructor(private dataService: DataService) {}

ngOnInit() {
this.dataService.data$.subscribe((data) => {
this.items = data;
});
}
}

마지막으로 데이터 업데이트를 수신하고 ChangeDetectorRef를 사용하는 하위 컴포넌트를 만듭니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { Component, Input, OnInit, ChangeDetectorRef } from '@angular/core';
import { DataService } from './data.service';

@Component({
selector: 'app-child',
template: `
<div *ngIf="data">
<h3>Child Component {{ index }}</h3>
<p>{{ data }}</p>
</div>
`,
})
export class ChildComponent implements OnInit {
@Input() data: any;
@Input() index: number;

constructor(private cdr: ChangeDetectorRef, private dataService: DataService) {}

ngOnInit() {
this.cdr.detach();

this.dataService.data$.subscribe((data) => {
const newData = data[this.index];

if (newData !== this.data) {
this.data = newData;
this.cdr.markForCheck();
}
});
}
}

이 예에서는 detach()를 사용하여 변경 감지 트리에서 하위 컴포넌트를 분리했습니다. 그런 다음 markForCheck()를 사용하여 새 데이터가 수신될 때 변경 사항 감지를 위한 컴포넌트를 표시했습니다.

결론

ChangeDetectorRef는 개발자에게 Angular의 변경 감지 프로세스를 수동으로 제어할 수 있는 강력한 방법을 제공합니다. 기능을 이해하고 방법을 활용하면 성능을 위해 애플리케이션을 최적화하고 복잡한 사용 사례를 쉽게 처리할 수 있습니다.

Share