Angular: ChangeDetectorRef 남용을 멈추세요

발행: (2026년 1월 16일 오후 06:18 GMT+9)
8 min read
원문: Dev.to

I’m happy to translate the article for you, but I’ll need the actual text you’d like translated. Could you please paste the content (or the portion you want translated) here? I’ll keep the source link and all formatting exactly as you requested.

Angular 개발에서는 change detection issues에 자주 직면합니다. 예를 들어 템플릿에서 일부 fields are not updated properly가 발생할 때가 그렇습니다. 이때 문제의 원인을 조사해야 하며, AI가 이제 일상 업무의 일부가 되었기 때문에 종종 이를 활용해 근본 원인을 파악합니다.

하지만 저는 Copilot suggests가 다양한 문제에 대해 detectChanges() 또는 **markForCheck()**를 빠른 해결책으로 제안하는 경우를 많이 보았습니다. 대부분의 경우 이러한 메서드들은 do not solve the root cause입니다. 실제 문제는 보통 컴포넌트 간 데이터 흐름이 잘못됐거나 아키텍처가 부실한 경우입니다. 이러한 메서드들은 특정 상황에서만 사용해야 하며, not as a workaround for misunderstanding or overusing the OnPush change detection strategy.

Source:

변경 감지 전략 이해

메서드설명
ChangeDetectorRef.detectChanges()현재 컴포넌트와 그 자식 컴포넌트에 대해 즉시 변경 감지를 실행합니다.
ChangeDetectorRef.markForCheck()뷰를 명시적으로 변경된 것으로 표시하여 다음 감지 사이클에서 다시 검사될 수 있게 합니다.

ChangeDetectionStrategy.Default

Angular는 모든 컴포넌트를 매 변경 감지 사이클마다 검사합니다.

ChangeDetectionStrategy.OnPush

Angular는 다음 경우에만 컴포넌트를 검사합니다:

  • @Input 참조가 변경될 때
  • async 파이프가 새로운 값을 받을 때
  • 템플릿에서 이벤트가 발생할 때
  • detectChanges() 또는 markForCheck()가 호출될 때

NOTE: 변경 감지와 관련해서 zone.js도 언급하는 것이 중요합니다. Angular의 zone.js 라이브러리는 setTimeout, HTTP 요청, 이벤트 핸들러와 같은 비동기 작업이 끝난 뒤 자동으로 변경 감지를 트리거합니다. 따라서 수동으로 detectChanges()를 호출하는 경우는 드물게 필요합니다.

Common Anti‑patterns

detectChanges()

@Component({
  selector: 'app-user',
  template: '{{ user?.name }}',
  changeDetection: ChangeDetectionStrategy.Default
})
export class UserComponent {
  @Input() user: User | undefined;

  constructor(private cdr: ChangeDetectorRef) {}

  // ❌ WRONG: Mutating @Input and calling detectChanges()
  updateUser(user: User) {
    this.user = user;
    this.cdr.detectChanges(); // Doesn't fix the architecture problem
  }
}

Problems

  1. user는 내부 updateUser 메서드가 아니라 부모 컴포넌트의 @Input()을 통해 업데이트되어야 합니다.
  2. detectChanges()를 호출할 필요가 없습니다. Angular가 자동으로 변화를 감지해야 합니다.

두 문제 모두 컴포넌트 아키텍처에 문제가 있음을 나타냅니다.

@Component({
  selector: 'app-data',
  template: '{{ data }}',
  changeDetection: ChangeDetectionStrategy.Default
})
export class DataComponent {
  data = '';

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

  // ❌ WRONG: Using detectChanges() instead of fixing data flow
  loadData() {
    this.service.getData().subscribe(result => {
      this.data = result;
      this.cdr.detectChanges(); // Workaround for bad architecture
    });
  }
}

ProblemdetectChanges()가 구독 내부에서 사용되었습니다. Observable이 방출된 후 Angular가 이미 변경 감지를 수행하므로, 이는 데이터 흐름이나 아키텍처가 잘못되었다는 신호입니다.

markForCheck()

@Component({
  selector: 'app-user-list',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: '{{ user.name }}'
})
export class UserListComponent {
  @Input() users: User[] = [];

  constructor(private cdr: ChangeDetectorRef) {}

  // ❌ WRONG: Mutating @Input internally and then calling markForCheck()
  addUser(user: User) {
    this.users.push(user);
    this.cdr.markForCheck();
  }
}

ProblemOnPush에서는 입력값을 불변으로 다루어야 합니다. 부모 컴포넌트는 기존 배열을 변형하는 대신 새 배열 레퍼런스(this.users = [...this.users, user])를 생성해야 합니다.

// ❌ WRONG: OnPush component should receive data via inputs
@Component({
  selector: 'app-user',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: '{{ user.name }}'
})
export class UserComponent {
  user: User = { name: 'John', age: 30 };

  constructor(
    private service: UserService,
    private cdr: ChangeDetectorRef
  ) {
    this.service.userNameChanged$.subscribe(userName => {
      this.user.name = userName;
      this.cdr.markForCheck();
    });
  }
}

Problem – 컴포넌트가 내부 user 객체를 직접 변형하고 markForCheck()로 강제로 업데이트합니다. 더 좋은 방법은 user@Input()으로 노출하고, 이름이 변경될 때마다 부모가 새 객체 레퍼런스를 전달하도록 하는 것입니다.

markForCheck()는 언제 허용되는가?

OnPush는 컴포넌트가 불변 입력단방향 데이터 흐름에 의해 구동될 때 가장 잘 작동합니다. markForCheck()에 대한 타당한 경우가 있습니다(예: 서비스가 async pipe를 통해 바인딩되지 않은 값을 방출할 때), 그러나 이를 자주 호출하고 있다면 해당 컴포넌트에 OnPush가 올바른 선택인지 재고해 보세요.

결론

ChangeDetectorRef는 강력한 도구이지만, 자주 의존한다면 아키텍처에 문제가 있음을 나타냅니다. markForCheck()detectChanges()근본적인 문제를 해결하기보다 가리는 경우가 많습니다.

대신 올바른 데이터 흐름에 집중하세요:

  • 불변 객체를 사용하고 입력값을 새로운 레퍼런스로 교체합니다.
  • 옵저버블 스트림에는 async 파이프를 활용합니다.
  • 컴포넌트를 작고 집중된 단위로 유지하고, 상태 관리는 부모 컴포넌트가 담당하도록 합니다.

데이터 흐름이 올바르면 Angular의 내장 변경 감지( OnPush 사용 여부와 관계없이) 가 UI를 자동으로 동기화하므로 수동 개입이 필요하지 않습니다.

옵저버블을 사용할 때는 OnPush와 함께 불변성을 유지하고, 데이터가 변경될 때마다 컴포넌트에 새로운 레퍼런스를 전달하도록 합니다. 이러한 메서드를 정기적으로 호출하고 있다면, 데이터 흐름을 재검토하고 설계를 다시 해야 할 시점입니다.

Advice: ChangeDetectorRef와 관련된 AI 제안을 시작점으로만 여기고 최종 답변으로 받아들이지 마세요. ChangeDetectorRef특정 상황에서는 실제 문제를 해결할 수 있지만, 문제의 근원이 데이터 흐름이나 컴포넌트 설계에 있는지 잠시 멈춰서 재확인하는 것이 중요합니다.

Back to Blog

관련 글

더 보기 »

Angular 팁 #4

소개: Angular 작업을 위한 몇 가지 팁 – 프론트엔드 개발자 관점에서 Part 4. 이 팁들은 이미 Angular 경험이 있다고 가정하므로, 우리는 …

Angular 21 — 새로운 점, 변경된 점

Angular 21은 단순화, 성능, 그리고 현대적인 reactive patterns에 중점을 둡니다. 화려한 APIs를 추가하기보다는 Angular 개발자들이 이미 사용하고 있는 것을 강화합니다.