IT story

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

hot-time 2020. 7. 12. 09:33
반응형

구성 요소 속성이 현재 날짜 시간에 의존하는 경우 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();
}

이것은이 오류를 이해하기에 좋은 게시물입니다. 읽기에 너무 길지 않습니다.


이 문제에 대한 작은 해결 방법 :

ngAfterViewInit() { // or ngOnInit or whatever
    setTimeout(() => {
        this.dateNow = new Date();
    });
}

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

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

1)에서 ngAfterViewInit이동ngAfterContentInit

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

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


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

}

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

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

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


I think the best and cleanest solution you can imagine is this:

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

Tested with Angular 5.2.9


Here is a small extract from tomonari_t answer about the causes for this error, i've tried to include only the parts that helped me to understand this.

The full article shows real code examples about every point shown here.

The root cause is angular lifecycle's:

After each operation Angular remembers what values it used to perform an operation. They are stored in the oldValues property of the component view.

After the checks have been done for all components Angular then starts the next digest cycle but instead of performing operations it compares the current values with the ones it remembers from the previous digest cycle.

The following operations are the being checked at digest cycle's:

check that values passed down to the child components are the same as the values that would be used to update properties of these components now.

check that values used to update the DOM elements are the same as the values that would be used to update these elements now perform the same.

checks for all child components

And so, the error is thrown when the compared values are different., blogger Max Koretskyi stated:

The culprit is always the child component or a directive.

And finally here are some real world samples that usually cause this error:

  • Shared services
  • Synchronous event broadcasting
  • Dynamic component instantiation

Every sample can be found here (plunkr), in my case the problem was a dynamic component instantiation.

Also, by my own experience i strongly recommend everyone to avoid the setTimeout solution, in my case caused an "almost" infinite loop (21 calls which i'm not willing to show you how to provoke them),

I would recommend to always keep in mind Angular lifecycle's so you can take in account how they would be affected everytime you modify another component's value. With this error Angular is telling you:

You're maybe doing this the wrong way, are you sure you're right?

The same blog also says:

Often, the fix is to use the right change detection hook to create a dynamic component

A short guide for me is to consider at least the following two things while coding (i'll try to complement it over time):

  1. Avoid modifing parent component values from it's childs components, instead: modify them from its parent.
  2. When you use @Input and @Output directives try to avoid triggering lyfecycle changes unless the component is completly initialized.
  3. Avoid unnecesary calls of this.cdr.detectChanges(); they can trigger more errors, specially when you'r dealing with a lot of dynamic data
  4. When the use of this.cdr.detectChanges(); is mandatory make sure that the variables (@Input, @Output, etc) being used are filled/initialized at right detection hook (OnInit, OnChanges, AfterView, etc)

Also

If you want to fully understand Angular Life Hook i recomend you to read the official documentation here:

참고URL : https://stackoverflow.com/questions/39787038/how-to-manage-angular2-expression-has-changed-after-it-was-checked-exception-w

반응형