구성 요소 속성이 현재 날짜 시간에 의존하는 경우 Angular2“확인 후 표현식이 변경되었습니다”예외를 관리하는 방법


167

내 구성 요소에는 현재 날짜 시간에 따라 다른 스타일이 있습니다. 내 구성 요소에는 다음 기능이 있습니다.

  private fontColor( dto : Dto ) : string {
    // date d'exécution du dto
    let dtoDate : Date = new Date( dto.LastExecution );

    (...)

    let color =  "hsl( " + hue + ", 80%, " + (maxLigness - lightnessAmp) + "%)";

    return color;
  }

lightnessAmp현재 날짜 시간부터 계산됩니다. dtoDate지난 24 시간 내에 있으면 색상이 바뀝니다 .

정확한 오류는 다음과 같습니다.

확인 후 표현식이 변경되었습니다. 이전 값 : 'hsl (123, 80 %, 49 %)'. 현재 값 : 'hsl (123, 80 %, 48 %)'

값이 확인되는 순간에만 예외가 개발 모드에 나타납니다. 확인 된 값이 업데이트 된 값과 다른 경우 예외가 발생합니다.

그래서 예외를 방지하기 위해 다음 후크 메소드에서 각 수명주기마다 현재 날짜 시간을 업데이트하려고했습니다.

  ngAfterViewChecked()
  {
    console.log( "! changement de la date du composant !" );
    this.dateNow = new Date();
  }

...하지만 성공하지 못했습니다.


이것은 문제를 이해하는 데 도움이 될 수 있습니다. Angular Debugging "표현 후 표현이 변경되었습니다": 간단한 설명 (및 수정)
deamon

답변:


356

변경 후 명시 적으로 변경 감지를 실행하십시오.

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

constructor(private cdRef:ChangeDetectorRef) {}

ngAfterViewChecked()
{
  console.log( "! changement de la date du composant !" );
  this.dateNow = new Date();
  this.cdRef.detectChanges();
}

완벽한 솔루션, 감사합니다. ngOnChanges, ngDoCheck, ngAfterContentChecked와 같은 후크 메소드에서도 작동한다는 것을 알았습니다. 가장 좋은 옵션이 있습니까?
Anthony Brenelière

27
사용 사례에 따라 다릅니다. 구성 요소가 초기화 될 때 무언가를하려면 ngOnInit()일반적으로 첫 번째 장소입니다. 코드가 렌더링되는 DOM에 의존 ngAfterViewInit()하거나 ngAfterContentInit()다음 옵션 인 경우 ngOnChanges()입력이 변경 될 때마다 코드를 실행해야하는 경우 적합합니다. ngDoCheck()맞춤 변경 감지 용입니다. 실제로 나는 무엇이 ngAfterViewChecked()가장 잘 사용 되는지 모른다 . 나는 그것이 바로 전후에라고 생각합니다 ngAfterViewInit().
Günter Zöchbauer

2
@KushalJayswal 죄송합니다. 설명에 이해가되지 않습니다. 달성하려는 것을 보여주는 코드로 새로운 질문을 작성하는 것이 좋습니다. 이상적으로 StackBlitz 예제가 있습니다.
Günter Zöchbauer 2018 년

4
구성 요소 상태가 브라우저 clientWidth등에서 계산 된 DOM 속성 등을 기반으로하는 경우에도 훌륭한 솔루션입니다 .
Jonah

1
ngAfterViewInit에서 방금 사용한 것처럼 매력처럼 작동했습니다.
Francisco Arleo

41

TL; DR

ngAfterViewInit() {
    setTimeout(() => {
        this.dateNow = new Date();
    });
}

이것이 해결 방법이지만 때로는 더 좋은 방법 으로이 문제를 해결하기가 어려우 므로이 방법을 사용하고 있다고 스스로 비난하지 마십시오. 괜찮아요.

: 초기 문제 [ 링크 해결할] setTimeout()[ 링크 ]


피하는 법

일반적으로이 오류는 일반적으로 어딘가에 추가 한 후에 (부모 / 자식 구성 요소에서도) 발생 ngAfterViewInit합니다. 첫 번째 질문은 스스로에게 물어 보는 것입니다 ngAfterViewInit. 아마도 코드를 다른 곳으로 옮길 ngAfterViewChecked수 있습니다 ( 대안 일 수도 있음).

: [ 링크 ]


또한

또한 ngAfterViewInitDOM에 영향을 미치는 비동기 항목으로 인해 발생할 수 있습니다. 파이프 를 통해 setTimeout또는 delay(0)연산자를 추가하여 해결할 수도 있습니다 .

ngAfterViewInit() {
  this.foo$
    .pipe(delay(0)) //"delay" here is an alternative to setTimeout()
    .subscribe();
}

: [ 링크 ]


좋은 독서

이것을 디버깅하는 방법과 그 이유에 대한 좋은 기사 : link


2
선택한 솔루션보다 속도가 느린 것 같습니다
Pete B

6
이것은 최선의 해결책은 아니지만 항상 작동합니다. 선택한 대답은 항상 (아주 잘 후크를 이해하는 하나의 요구는 작업을 진행하게) 작동하지 않습니다
프레디 Bonda

이것이 왜 효과가 있는지에 대한 올바른 설명은 무엇입니까? 그런 다음 다른 스레드 (비동기)에서 실행되기 때문입니까?
knnhcn

3
@knnhcn 자바 스크립트에는 다른 스레드와 같은 것이 없습니다. JS는 본질적으로 단일 스레드입니다. SetTimeout 은 타이머가 만료 된 후 얼마 후에 기능을 실행하도록 엔진에 지시합니다 . 여기서 타이머는 0이며, 현대 브라우저에서 실제로 4로 처리되며, 각이 마법의 일을 할 수있는 충분한 시간입니다. developer.mozilla.org/en-US/docs/Web/API/…
Kilves

26

github 문제 에 @leocaseiro가 언급했듯이 .

쉬운 수정을 원하는 사람들을 위해 3 가지 솔루션을 찾았습니다.

1)에서 ngAfterViewInit로 이동ngAfterContentInit

2) # 14748 (의견)에서 제안한대로 ngAfterViewChecked합병ChangeDetectorRef

3) ngOnInit ()을 유지하면서 ChangeDetectorRef.detectChanges()변경 후 호출 하십시오.


1
누구든지 가장 권장되는 솔루션을 문서화 할 수 있습니까?
Pipo

13

그녀는 두 가지 해결책을 간다!

1. ChangeDetectionStrategy 를 OnPush로 수정

이 솔루션의 경우 기본적으로 각도를 말합니다.

변경 사항 확인을 중지하십시오. 내가 필요할 때만 할게

빠른 수정 사항 :

사용하도록 구성 요소 수정 ChangeDetectionStrategy.OnPush

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent implements OnInit {
    // ...
}

이로 인해 더 이상 작동하지 않는 것 같습니다. 지금부터 Angular를 detectChanges()수동으로 호출 해야하기 때문입니다.

this.cdr.detectChanges();

다음은 ChangeDetectionStrategy를 이해하는 데 도움이되는 링크입니다 . https://alligator.io/angular/change-detection-strategy/

2. ExpressionChangedAfterItHasBeenCheckedError 이해

다음은 이 오류의 원인에 대한 tomonari_t 답변 의 작은 추출물입니다 .이를 이해하는 데 도움이되는 부분 만 포함하려고했습니다.

전체 기사는 여기에 표시된 모든 요점에 대한 실제 코드 예제를 보여줍니다.

근본 원인은 각도 수명주기입니다.

각 작업 후에 Angular는 작업을 수행하는 데 사용한 값을 기억합니다. 구성 요소보기의 oldValues ​​특성에 저장됩니다.

모든 구성 요소에 대해 점검이 완료된 후 Angular는 다음 다이제스트 사이클을 시작하지만 작업을 수행하는 대신 현재 값을 이전 다이제스트 사이클에서 기억 한 값과 비교합니다.

다이제스트 사이클에서 다음 작업을 확인하고 있습니다.

하위 구성 요소로 전달 된 값이 현재 이러한 구성 요소의 특성을 업데이트하는 데 사용되는 값과 동일한 지 확인하십시오.

DOM 요소를 업데이트하는 데 사용 된 값이이 요소를 업데이트하는 데 사용되는 값과 동일한 지 확인하십시오.

모든 하위 구성 요소를 확인합니다

따라서 비교 된 값이 다르면 오류가 발생합니다. 블로거 Max Koretskyi 는 다음과 같이 말했습니다.

범인은 항상 자식 구성 요소 또는 지시어입니다.

마지막으로 일반적으로이 오류를 발생시키는 실제 샘플이 있습니다.

  • 공유 서비스
  • 동기 이벤트 방송
  • 동적 컴포넌트 인스턴스화

모든 샘플은 여기 (plunkr)에서 찾을 수 있습니다 . 제 경우에는 문제가 동적 구성 요소 인스턴스화였습니다.

또한, 내 자신의 경험에 의해 모든 사람을 피하는 것이 좋습니다 setTimeout 솔루션 . 제 경우에는 "거의"무한 루프를 일으켰습니다.

Angular 라이프 사이클을 항상 기억하여 다른 구성 요소의 값을 수정할 때마다 영향을받는 방식을 고려하는 것이 좋습니다. 이 오류로 Angular가 알려줍니다.

당신은 아마 이것을 잘못하고있을 것입니다. 확실합니까?

동일한 블로그에서도 다음과 같이 말합니다.

종종 수정은 올바른 변경 감지 후크를 사용하여 동적 구성 요소를 작성하는 것입니다.

나를위한 짧은 가이드는 코딩하는 동안 적어도 다음 두 가지를 고려하는 것입니다 ( 시간이 지남에 따라 보완하려고 노력할 것입니다 ).

  1. 자식 구성 요소에서 부모 구성 요소 값을 수정하지 말고 대신 부모 구성 요소 값을 수정하십시오.
  2. 사용 @Input하고 @Output지시문 을 사용할 때 구성 요소가 완전히 초기화되지 않으면 lyfecycle 변경 트리거를 피하십시오.
  3. 불필요한 전화를 피하십시오 this.cdr.detectChanges(); 동적 특히 많은 동적 데이터를 처리 할 때 더 많은 오류를 유발할 수 있습니다.
  4. this.cdr.detectChanges();필수 사용시 사용 @Input, @Output, etc중인 변수 ( )가 오른쪽 감지 후크 ( OnInit, OnChanges, AfterView, etc) 에서 채워지거나 초기화 되었는지 확인하십시오.
  5. 가능한 경우 hide 대신 제거하십시오 . 이는 포인트 3 및 4와 관련이 있습니다.

또한

Angular Life Hook를 완전히 이해하려면 여기에서 공식 문서를 읽는 것이 좋습니다.


을 변경하는 이유를 나는 아직도 이해할 수 없다 ChangeDetectionStrategyOnPush나를 위해 고정 그것. 간단한 구성 요소가 [disabled]="isLastPage()"있습니다. 이 메소드는 MatPaginator-ViewChild를 읽고 리턴 this.paginator !== undefined ? this.paginator.pageIndex === this.paginator.getNumberOfPages() - 1 : true;합니다. 페이지 매김 기능은 즉시 사용할 수 없지만을 사용하여 바인딩 한 후에는 사용할 수 없습니다 @ViewChild. (가) 변경 ChangeDetectionStrategy오류를 제거 - 그리고 기능은 여전히 예전처럼 존재한다. 현재 어떤 단점이 있는지 잘 모르겠지만 감사합니다!
Igor

좋은 @Igor입니다! 사용의 유일한 철회는 구성 요소를 새로 고칠 때마다 OnPush사용해야한다는 것 this.cdr.detectChanges()입니다. 당신이 이미 그것을 사용하고 있다고 생각합니다
Luis Limas

9

이 경우 구성 요소에 changeDetection을 추가하고 ngAfterContentChecked에서 detectChanges ()를 호출하여 수정했습니다.

@Component({
  selector: 'app-spinner',
  templateUrl: './spinner.component.html',
  styleUrls: ['./spinner.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class SpinnerComponent implements OnInit, OnDestroy, AfterContentChecked {

  show = false;

  private subscription: Subscription;

  constructor(private spinnerService: SpinnerService, private changeDedectionRef: ChangeDetectorRef) { }

  ngOnInit() {
    this.subscription = this.spinnerService.spinnerState
      .subscribe((state: SpinnerState) => {
        this.show = state.show;
      });
  }

  ngAfterContentChecked(): void {
      this.changeDedectionRef.detectChanges();
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }

}

2

나는 당신이 상상할 수 있는 가장 좋고 가장 깨끗한 해결책 은 이것 이라고 생각 합니다 .

@Component( {
  selector: 'app-my-component',
  template: `<p>{{ myData?.anyfield }}</p>`,
  styles: [ '' ]
} )
export class MyComponent implements OnInit {
  private myData;

  constructor( private myService: MyService ) { }

  ngOnInit( ) {
    /* 
      async .. await 
      clears the ExpressionChangedAfterItHasBeenCheckedError exception.
    */
    this.myService.myObservable.subscribe(
      async (data) => { this.myData = await data }
    );
  }
}

Angular 5.2.9로 테스트


3
이것은 해키 .. 그리고 너무 unneceesairy입니다.
JoeriShoeby

1
@JoeriShoeby 위의 다른 모든 솔루션은 setTimeouts 또는 고급 Angular 이벤트를 기반으로 한 해결 방법입니다 ...이 솔루션은 모든 주요 브라우저에서 지원되는 순수한 ES2017입니다. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… caniuse.com / # search = await
JavierFuentes

1
예를 들어 ES2017 기능을 신뢰할 수없는 Angular + Cordova를 사용하여 모바일 애플리케이션 (Android API 17을 대상으로 함)을 구축하는 경우 어떻게됩니까? 허용 된 답변은 해결책이 아니라 해결책입니다 ..
JoeriShoeby

@JoeriShoeby typescript를 사용하여 JS로 컴파일되므로 기능 지원 문제가 없습니다.
biggbest

@biggbest 그게 무슨 뜻입니까? Typescript가 JS로 컴파일된다는 것을 알고 있지만 모든 JS가 작동한다고 가정 할 수는 없습니다. Javascript는 웹 브라우저에서 실행되며 모든 브라우저가 다르게 작동합니다.
JoeriShoeby

2

주변의 작은 작업은 여러 번 사용했습니다.

Promise.resolve(null).then(() => {
    console.log( "! changement de la date du composant !" );
    this.dateNow = new Date();
    this.cdRef.detectChanges();
});

나는 주로 컨트롤러에서 사용하는 변수로 "널"을 대체합니다.


1

이미 많은 답변과 변경 감지에 대한 훌륭한 기사 링크가 있지만 여기에 2 센트를주고 싶었습니다. 체크가 이유가 있다고 생각하므로 앱의 아키텍처에 대해 생각 BehaviourSubject하고 올바른 수명주기 후크를 사용하여보기의 변경 사항을 처리 할 수 ​​있음을 깨달았습니다 . 그래서 여기 내가 해결책을 위해 한 일이 있습니다.

  • 타사 구성 요소 ( fullcalendar) 를 사용하지만 Angular Material 도 사용합니다 하므로 스타일 지정을위한 새로운 플러그인을 만들었지 만 일정 헤더를 사용자 정의 할 수 없기 때문에 모양과 느낌을 얻는 것이 약간 어색했습니다. 자신을 굴 리세요.
  • 그래서 기본 JavaScript 클래스를 얻었고 구성 요소에 대한 내 캘린더 헤더를 초기화해야합니다. ViewChild부모님이 렌더링되기 전에 를 렌더링 해야 합니다. Angular가 작동하는 방식이 아닙니다. 그렇기 때문에 템플릿에 필요한 값을 다음과 같이 래핑했습니다 BehaviourSubject<View>(null).

    calendarView$ = new BehaviorSubject<View>(null);

다음으로보기가 확인되었는지 확인할 수 있으면 해당 주제를 다음의 값으로 업데이트합니다 @ViewChild.

  ngAfterViewInit(): void {
    // ViewChild is available here, so get the JS API
    this.calendarApi = this.calendar.getApi();
  }

  ngAfterViewChecked(): void {
    // The view has been checked and I know that the View object from
    // fullcalendar is available, so emit it.
    this.calendarView$.next(this.calendarApi.view);
  }

그런 다음 템플릿에서 async 파이프를 . 변경 감지를 통한 해킹이나 오류가 없으며 원활하게 작동합니다.

자세한 내용이 필요하면 언제든지 문의하십시오.


1

오류를 피하려면 기본 양식 값을 사용하십시오.

ngAfterViewInit ()에 detectChanges ()를 적용하는 승인 된 대답을 사용하는 대신 (내 경우에도 오류를 해결했습니다) 대신 동적으로 필요한 양식 필드의 기본값을 저장하여 양식이 나중에 업데이트 될 때 사용자가 새로운 필수 필드를 트리거하고 제출 단추를 사용 불가능하게하는 양식의 옵션을 변경하기로 결정하면 유효성이 변경되지 않습니다.

이것은 내 구성 요소에 약간의 코드를 저장했으며 제 경우에는 오류를 완전히 피했습니다.


이것은 당신에게 불필요하게 부르는 것을 피하는 정말 좋은 습관입니다this.cdr.detectChanges()
Luis Limas

1

변수를 선언하고 나중에
값을 사용하여 값 을 변경하고 싶기 때문에 오류가 발생 했습니다.ngAfterViewInit

export class SomeComponent {

    header: string;

}

내가 전환 한 것을 고치기 위해

ngAfterViewInit() { 

    // change variable value here...
}

ngAfterContentInit() {

    // change variable value here...
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.