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

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

  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();
  }

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



답변

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

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();
}


답변

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


답변

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

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

1)에서 ngAfterViewInit로 이동ngAfterContentInit

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

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


답변

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

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를 완전히 이해하려면 여기에서 공식 문서를 읽는 것이 좋습니다.


답변

이 경우 구성 요소에 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();
  }

}


답변

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

@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로 테스트


답변

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

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

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