ExpressionChangedAfterItHasBeenCheckedError Explained

前端 未结 26 1556
慢半拍i
慢半拍i 2020-11-22 14:46

Please explain to me why I keep getting this error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.

Obviously,

相关标签:
26条回答
  • 2020-11-22 15:31

    I'm using ng2-carouselamos (Angular 8 and Bootstrap 4)

    Taking these steps fixed my problem:

    1. Implement AfterViewChecked
    2. Add constructor(private changeDetector : ChangeDetectorRef ) {}
    3. Then ngAfterViewChecked(){ this.changeDetector.detectChanges(); }
    0 讨论(0)
  • 2020-11-22 15:31

    @HostBinding can be a confusing source of this error.

    For example, lets say you have the following host binding in a component

    // image-carousel.component.ts
    @HostBinding('style.background') 
    style_groupBG: string;
    

    For simplicity, lets say this property is updated via the following input property:

    @Input('carouselConfig')
    public set carouselConfig(carouselConfig: string) 
    {
        this.style_groupBG = carouselConfig.bgColor;   
    }
    

    In the parent component you are programatically setting it in ngAfterViewInit

    @ViewChild(ImageCarousel) carousel: ImageCarousel;
    
    ngAfterViewInit()
    {
        this.carousel.carouselConfig = { bgColor: 'red' };
    }
    

    Here's what happens :

    • Your parent component is created
    • The ImageCarousel component is created, and assigned to carousel (via ViewChild)
    • We can't access carousel until ngAfterViewInit() (it will be null)
    • We assign the configuration, which sets style_groupBG = 'red'
    • This in turn sets background: red on the host ImageCarousel component
    • This component is 'owned' by your parent component, so when it checks for changes it finds a change on carousel.style.background and isn't clever enough to know that this isn't a problem so it throws the exception.

    One solution is to introduce another wrapper div insider ImageCarousel and set the background color on that, but then you don't get some of the benefits of using HostBinding (such as allowing the parent to control the full bounds of the object).

    The better solution, in the parent component is to add detectChanges() after setting the config.

    ngAfterViewInit()
    {
        this.carousel.carouselConfig = { ... };
        this.cdr.detectChanges();
    }
    

    This may look quite obvious set out like this, and very similar to other answers but there's a subtle difference.

    Consider the case where you don't add @HostBinding until later during development. Suddenly you get this error and it doesn't seem to make any sense.

    0 讨论(0)
  • 2020-11-22 15:33

    A lot of understanding came once I understood the Angular Lifecycle Hooks and their relationship with change detection.

    I was trying to get Angular to update a global flag bound to the *ngIf of an element, and I was trying to change that flag inside of the ngOnInit() life cycle hook of another component.

    According to the documentation, this method is called after Angular has already detected changes:

    Called once, after the first ngOnChanges().

    So updating the flag inside of ngOnChanges() won't initiate change detection. Then, once change detection has naturally triggered again, the flag's value has changed and the error is thrown.

    In my case, I changed this:

    constructor(private globalEventsService: GlobalEventsService) {
    
    }
    
    ngOnInit() {
        this.globalEventsService.showCheckoutHeader = true;
    }
    

    To this:

    constructor(private globalEventsService: GlobalEventsService) {
        this.globalEventsService.showCheckoutHeader = true;
    }
    
    ngOnInit() {
    
    }
    

    and it fixed the problem :)

    0 讨论(0)
  • 2020-11-22 15:35

    Follow the below steps:

    1. Use 'ChangeDetectorRef' by importing it from @angular/core as follows:

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

    2. Implement it in constructor() as follows:

    constructor(   private cdRef : ChangeDetectorRef  ) {}
    

    3. Add the following method to your function which you are calling on an event like click of button. So it look like this:

    functionName() {   
        yourCode;  
        //add this line to get rid of the error  
        this.cdRef.detectChanges();     
    }
    
    0 讨论(0)
  • 2020-11-22 15:36

    I had this sort of error in Ionic3 (which uses Angular 4 as part of it's technology stack).

    For me it was doing this:

    <ion-icon [name]="getFavIconName()"></ion-icon>

    So I was trying to conditionally change the type of an ion-icon from a pin to a remove-circle, per a mode a screen was operating on.

    I'm guessing I'll have to add an *ngIf instead.

    0 讨论(0)
  • 2020-11-22 15:38

    Referring to the article https://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4

    So the mechanics behind change detection actually works in a way that both change detection and verification digests are performed synchronously. That means, if we update properties asynchronously the values will not be updated when the verification loop is running and we will not get ExpressionChanged... error. The reason we get this error is, during the verification process, Angular sees different values then what it recorded during change detection phase. So to avoid that....

    1) Use changeDetectorRef

    2) use setTimeOut. This will execute your code in another VM as a macro-task. Angular will not see these changes during verification process and you will not get that error.

     setTimeout(() => {
            this.isLoading = true;
        });
    

    3) If you really want to execute your code on same VM use like

    Promise.resolve(null).then(() => this.isLoading = true);
    

    This will create a micro-task. The micro-task queue is processed after the current synchronous code has finished executing hence the update to the property will happen after the verification step.

    0 讨论(0)
提交回复
热议问题