Angular “expression has changed after it was checked” 예외 — 현재 시간에 의존하는 속성의 근본 원인과 해결책

1. 문제 정의

Angular 2.x 개발 템플릿 모드에서, 컴포넌트 속성이 현재 datetime에 의존할 때 다음과 같은 예외가 발생합니다.

1
Expression has changed after it was checked

이 예외는 Angular가 템플릿 바인딩의 일관성을 검증하는 동안, 검사 시점과 실제 바인딩 시점 사이에 표현식의 값이 달라졌을 때 던집니다. 프로덕션 빌드에서는 잘 드러나지 않고, 개발 모드(비 프로덕션 빌드)에서 더 쉽게 관찰됩니다.

원본 질문의 코드를 보면 dateNow 프로퍼티가 컴포넌트의 시각 관련 스타일(글꼴 색상) 계산에 쓰이고, 이 값이 계속 바뀔 수 있습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 색상 계산 함수 — 현재 시간에 따라 색상이 달라짐
private fontColor( dto : Dto ) : string {
  // dto의 최근 실행 시각
  let dtoDate : Date = new Date( dto.LastExecution );
  (...)
  let color =  "hsl( " + hue + ", 80%, " + (maxLigness - lightnessAmp) + "%)";
  return color;
}

// 뷰 체크 훅에서 현재 시각을 갱신
ngAfterViewChecked() {
  console.log( "! changement de la date du composant !" );
  this.dateNow = new Date();
}

ngAfterViewChecked() 훅에서 this.dateNow = new Date()로 컴포넌트 속성을 바꾸었기 때문에, 템플릿이 이 표현식을 이미 검사한 뒤 값이 변해 예외가 발생한 것입니다.

2. 표면 증상

구분증상판단 포인트
발생 시점개발 모드 런타임, 뷰 검사 단계Angular 런타임 검증기가 동작 중
예외 코드Expression has changed after it was checked바인딩 값을 표현식에서 읽는 동안 값이 변경됨
관련 함수ngAfterViewChecked뷰 초기화 후 실행되는 훅 — 여기서 부작용을 만듦
진짜 원인현재 datetime처럼 계속 변하는 표현식이 템플릿에 바인딩됨값이 매 순간 변해 검사·바인딩 시점이 어긋남

증상 fingerprint

  • 컴포넌트 속성(또는 뷰 모델 값)이 new Date(), 타임스탬프 계산처럼 외부에서 계속 바뀌는 표현식에 의존한다.
  • 그 값을 컴포넌트의 어떤 훅(라이프사이클 후반 단계) 안에서 덮어쓴다.
  • 개발 모드 빌드에서만 자주 나타나 배포 환경에서는 재현이 잘 안 된다.

이 조건이 하나라도 해당되면, 이 예외가 어느 컴포넌트에서 발생하는지 우선 점검해 둘 가치가 있습니다.

3. 원인 탐구

Angular의 기본 변경 감지(Change Detection)는 템플릿 표현식의 값을 한 번 판단하고, 그 값이 유효한 동안에는 재평가하지 않도록 동작합니다. 그런데 그 표현식이 순수하지 않고 계속 변하는 값에 의존하고 있다면:

  1. Angular가 템플릿 표현식 this.dateNow를 먼저 검사한다.
  2. 같은 렌더링 사이클 안에서, 개발자 코드가 this.dateNow = new Date() 같은 방식으로 그 값을 바꾼다.
  3. Angular가 검사 당시 기억한 값과 현재 실제 값을 비교하다가 값이 달라졌음을 감지하고 예외를 던진다.

질문자가 겪은 시나리오는 전형적인 “검사 이후 값이 반영되는 시점의 어긋남” 패턴입니다.

4. 근본 원인 분석

Expression has changed after it was checked의 본질은 “값을 바꿨다는 사실 자체"가 아니라, 바꾼 것이 Angular 변경 감지 시스템에 통지되지 않았다는 점입니다.

Angular 템플릿 엔진은 미리 검사한 바인딩 값을 두고 다시 재평가하지 않는 가정으로 동작합니다. 그런데 개발자 코드가 Angular의 통지 메커니즘을 거치지 않고 직접 속성을 변경하면, 이후 렌더링 시점에 Angular는 “아까 검증한 값이 이제 맞지 않음"을 인지하게 됩니다.

즉 근본 원인은 “값의 변경을 Angular가 다시 평가하도록 언제 알려야 하는가"라는 시점 문제입니다. 값을 바꾸는 것 자체가 잘못된 것이 아니라, 정합성이 유지되도록 Angular에게 명시적으로 변경을 통지(detectChanges)하는 것이 해결의 방향입니다.

5. 코드 해결책

5.1 ChangeDetectorRef 주입 + detectChanges() 호출

@angular/coreChangeDetectorRef를 생성자에 주입하고, 값을 바꾸는 훅(여기서는 ngAfterViewChecked)에서 변경 감지를 명시적으로 호출합니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import { ChangeDetectorRef } from '@angular/core';

export class MyComponent {
  // 값이 계속 변하는 속성
  dateNow: Date;

  constructor(private cdRef: ChangeDetectorRef) { }

  ngAfterViewChecked() {
    console.log( "! valeur modifiée de la date dans la composante !" );
    this.dateNow = new Date();

    // ★ 핵심: 변경 감지를 명시적으로 재실행
    this.cdRef.detectChanges();
  }
}

detectChanges() 호출은 Angular에게 “표현식 값이 다시 변동됐으니 재평가하고 바인딩에 반영하라"고 알려줍니다. 이로써 검사된 표현식이 어긋나는 상태가 해소되어 예외로 끝나지 않습니다.

원리 요약

시점동작
ngAfterViewChecked()현재 시각 갱신 + detectChanges() 호출
Angular 변경 감지detectChanges() 신호로 표현식 재평가
이후 렌더링새 값이 바인딩되어 예외 없음

일반적인 점검 기준: 이 패턴은 Angular 데이터 개발 템플릿에서 “값을 바꾼 직후 반드시 변경을 통지하자"는 원칙의 일부입니다. 환경에 따라 다를 수 있으므로, 직접 속성을 바꾸는 로직에는 반드시 명시적인 변경 감지 통지가 따라와야 합니다.

5. 향후 예방 조치 — 같은 예외를 피하는 방법

  1. 동적 값에 의존하는 표현식은 라이프사이클 훅에서 직접 바꾸지 않는다ngAfterViewChecked 같은 후훅에서 값을 덮어쓰면 검사 시점과 충돌하기 쉽다.
  2. 값을 변경한다면 반드시 detectChanges()를 같은 흐름 안에서 호출한다 — 여러 곳에서 소비된다면 변경 직후 명시적으로 갱신한다.
  3. 부재 재현(Side Effect)이 있는 표현식을 템플릿에 직접 쓰기보다 컴포넌트 필드로 사전 계산dateNow처럼 값을 한 번 확정해 참조하면 변경 감지와 충돌을 줄인다.
  4. 개발 모드 검증 후 프로덕션 빌드에서도 확인 — 개발 모드에서 발생한 예외는 프로덕션 런타임에서 재현이 어려울 수 있어, 배포 전후로 반복 확인한다.

6. 검증 명령

코드 변경 후 예외가 사라졌는지 아래 순서대로 확인합니다.

1
2
3
4
5
6
7
8
# 1) 컴포넌트/템플릿 관련 프로젝트 테스트 실행
npm test

# 2) 프로젝트 빌드로 타입/문법 오류 확인
npm build

# 3) 개발 서버를 실행해 해당 페이지·컴포넌트 동작 확인
npm serve

환경에 따라 다를 수 있습니다. Angular 프로젝트가 npm 기반이 아닌 다른 빌드 방식을 쓴다면 해당 도구의 검증 명령으로 대신하면 됩니다. 핵심 판단은 변경 후 페이지를 새로 고침하거나 컴포넌트를 다시 그릴 때 예외가 재현되지 않는지입니다.

7. DevTrace 결론

이 문제의 핵심은 “값을 바꾸는 행위"가 아니라, “Angular 변경 감지 시스템에 값을 바꿨다는 사실을 통지하지 않은 것” 입니다. ChangeDetectorRef.detectChanges() 호출로 표현식 재평가를 명시적으로 수행해야 “expression has changed after it was checked” 예외를 근본적으로 해소할 수 있습니다.


출처: StackOverflow — How to manage Angular2 “expression has changed after it was checked” exception when a component property depends on current datetime