angular: ExpressionChangedAfterItHasBeenCheckedError when changing a component value in afterViewInit

I’m submitting a …

[x] bug report
[ ] feature request
[ ] support request

Current behavior changing a component value in afterViewInit throws ExpressionChangedAfterItHasBeenCheckedError if the value has an impact on a method used in the view.

in the plunker: -> getHeightPx() returns 0px before ngAfterViewInit is called (isLoaded === undefined) -> getHeightPx() returns 100px after ngAfterViewInit is called (isLoaded === true)

Expected behavior it should not throw the error since getHeightPx is just a method and not part of the model.

Minimal reproduction of the problem with instructions http://plnkr.co/edit/JdhPkjBl6wc0FIc8LcUs?p=preview

  • Angular version: 4.0.0-rc.0 => KO
  • Angular version: 4.0.0-beta.8 => OK

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 53 (25 by maintainers)

Commits related to this issue

Most upvoted comments

it’s by design

On this similar issue, it is suggested that we run the change detection explicitly after the change. It works for me.

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

I found 3 solutions for who are looking for easy fixes.

  1. Moving from ngAfterViewInit to ngAfterContentInit

  2. Moving to ngAfterViewChecked combined with ChangeDetectorRef as suggested on https://github.com/angular/angular/issues/14748#issuecomment-307291715

  3. keep with ngOnInit() but call ChangeDetectorRef.detectChanges() after your changes.

UPDATE PS: If your updates are not related to DOM, you can use ChangeDectorRef.detectChanges() on ngOnInit() still, as @zoechi explains on StackOverFlow

Hi all,

If this error is by design, what’s the correct pattern to avoid this sort of issue? I have an observable and I’m using as follows

<ul class="pagination pagination-flat pagination-xs no-margin-bottom" *ngIf="dataPages$ | async as dataPages">
            <li *ngFor="let item of dataPages" [ngClass]="{active: item.isActive, disabled: item.isDisabled}">
                <a *ngIf="item.isDisabled"><i class="{{item.icon}}" *ngIf="item.icon"></i>{{item.displayText}}</a>
                <a *ngIf="!item.isDisabled" (click)="onPageClicked(item.pageNum)">
                    <i class="{{item.icon}}" *ngIf="item.icon"></i>
                    {{item.displayText}}
                </a>
            </li>
        </ul>

The paging values will change once the data is loaded and the paging is calculated. It keep throwing that error on dev mode. In prod, that error does not appear. So I’m wondering what’s the correct pattern to avoid this issue.

Any help would be appreciated!

@victornoel comments without a Plunker that demonstrates the problem are not really helpful

was able to work around this be using ngDoCheck() method and calling the ChangeDetectorRef.detectChanges method.

public ngDoCheck(): void {
    this.cdr.detectChanges();
}

You can inject this.cdRef:ChangeDetectorRef and then call this.cdRef.detectChanges() after you modified the model to work around the error.

@TheBolmanator please refrain from making claims that are based on your limited Angular knowledge 😉 When you complain about Angular without exact instructions how to reproduce the problem, it’s IMHO safe to treat the comments as spam.

Hi all.

This is a helpful post to understand and fix this error.

Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedError error

The only outcome is to train the operators to ignore the alarm.

That’s not true. It points out a possible error or architectural issue and it easy to fix There is no need to ignore it.

I also start to see ExpressionChangedAfterItHasBeenCheckedError after updating to 4.0.0-rc.1 from 4.0.0-beta.8 and 2.4.8.

https://plnkr.co/edit/atBNGvMgC3QxSGk7Sm6f?p=preview

Above plnkr shows Clarity’s datagrid without a problem with 4.0.0-beta.8. But, after commenting out beta.8 and uncommenting rc1 in the config.js file, this error is thrown.

ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: ‘Query 3 not dirty’. Current value: ‘Query 3 dirty’.

Was there any breaking change regarding the above between beta.8 and rc.1? If I run it with enableProdMode(), the error disappears.

ping @youdz

ok is see the difference.

  • If we directly modify a value binded to the view it throws the error as expected (since 2.0.0 at least)
  • If we modify a component value that indirectly modify the view (in the plunker isViewInit has an impact on the getTextDecoration method used in the view - via the ngStyle here) then it throws the error since 4.0.0-rc.1

http://plnkr.co/edit/I51JWge8P5oY0mj2Ni65?p=preview

@pappacurds and @imransilvake That’s not a great solution, what your doing is telling angular to run change detection every time change detection has occured, in other words, you’ve created an infinite loop…

@zoechi clearly what @TheBolmanator is saying is that no, there is no easy fix!

In this thread we see a lot of unanswered questions about how to manage this… This one is a good example: https://github.com/angular/angular/issues/14748#issuecomment-300976768. It seems that some of the error disappear if you play with ngIf and as in order to change the way things are interpreted at runtime… I’m pretty sure this is neither an easy fix (hard to maintain) and not an architectural issue…

It is not clear what is the source of these errors (nor how to fix them). Yes, in some cases, the problem is clear and you need to force change detection, but for the rest, there is many cases where you have an async pipe that makes this problem appear with no reason whatsoever.

What is the appropriate place to update UI menu to avoid this error?

@sandraacha9 nu inteleg ce spui…

Hi All,

I was having this issue and it was bugging me for a long time. I think I finally found a pattern that is avoiding receiving this problem. It seems to be essentially a synchronization issue.

Approach one: use NGRX and start managing the state of the objects you are handling and getting this sort of issue. To implement this, find the many tutorials available on the internet. Imo, this approach is only good when you are managing state of many objects across many components. Otherwise it adds a development overhead that you need to evaluate if truly is worthwhile.

Approach two: create a simple service that manages the state of this object using BehaviorSubject.

For this approach, check this out.

import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Injectable } from '@angular/core';

@Injectable()
export class SecurityLayoutStateService {

    addNewbuttonState$: BehaviorSubject<boolean>;

    constructor() {
        this.addNewbuttonState$ = new BehaviorSubject(false);
    }

    getAddNewButtonState() {
        return this.addNewbuttonState$.asObservable();
    }

    setAddNewButtonState(value: boolean) {
        this.addNewbuttonState$.next(value);
    }
}

So lets say you have a root component that is responsible to set the basic layout of the component and multiple sub-components that are basically different content but can re-use the root structure. The button Add new should only appear on the listing content and hide on edit mode.

<page-header [pageTitle]="'Security Management'"></page-header>
<div class="panel panel-white">
    <div class="panel-heading">
        <h6 class="panel-title">{{title}}</h6>
        <div class="heading-elements" *ngIf="showButton">
            <a class="btn btn-default" (click)="btnAddNew.emit()">
                <i class="icon-add position-left"></i> Add new
            </a>
        </div>
    </div>
    <div class="dataTables_wrapper no-footer">
        <div class="tabbable">
            ... omitted for brevity
           <router-outlet></router-outlet>
        </div>
        <div class="datable-footer panel-footer" *ngIf="isPagingAvailable">
            <span class="heading-text text-muted p-15">Showing page {{paging.currentPage$ | async}} out of {{paging.totalPages$ | async}}. Total items: {{paging.totalItems$ | async}}</span>
            <paging-component class="pull-right" (onLoad)="onLoad.emit($event)" #paging></paging-component>
        </div>
    </div>
</div>

The content received inside the <router-outlet></router-outlet> modifies the value of the showButton and throwing the annoying exception we are all seeing.

Modified the showButton to the following:

@Component({
    moduleId: module.id,
    selector: 'app-security',
    templateUrl: 'security.root.component.html'
})
export class SecurityRootComponent implements OnInit {
    constructor(private layoutStateService: SecurityLayoutStateService) { }

    ngOnInit() {
        ....
        this.addNewbuttonState$ = this.layoutStateService.getAddNewButtonState();
    }
}

<div class="heading-elements" *ngIf="addNewbuttonState$ | async">...</div>

Now that annoying message is no longer showing up and the load stays correct. Whenever you need to change the state of the button, simply inject the service and use the setter. Whatever component is subscribed to this Observable will get the update.

I hope this can help you all. Cheers

probably was a bug or it was disabled. anyway it throws in 2.x and it will throw in 4.x

@DzmitryShylovich humm indeed just saw it, but then why did that work in 4.0.0-beta.8 ? (and many previous version since the release)