Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked on ng 4 #17572

Closed
altreze opened this issue Jun 16, 2017 · 201 comments
Labels
area: core Issues related to the framework runtime regression Indicates than the issue relates to something that worked in a previous version type: bug/fix

Comments

@altreze
Copy link

altreze commented Jun 16, 2017

I'm submitting a ...


[ ] Regression (behavior that used to work and stopped working in a new release)
[X ] Bug report #14748 
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior

After using an observable object in my template using async, I am receiving :
ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: ''. Current value: 'something'

Expected behavior

Please tell us about your environment


Angular version: 4.2.2

Browser:
- [X] Chrome (desktop) version Version 58.0.3029.110 (64-bit)
 
For Tooling issues:
- Node version: v6.10.3
- Platform: Mac
@Bigless27
Copy link

Bigless27 commented Jun 16, 2017

Had this same issue with using redux. Here is a link to issue I posted on their repository. When on angular 2 there are no errors but on angular 4.2.2 I get expression changed after it was checked errors.
angular-redux/store#428

@mpalourdio
Copy link

The problem appears for me on my components when bumping from 4.1.3 to 4.2.x. Reverting to 4.1.3 fixes the problem for me, but that's not something i want to keep forever.

I could reproduce on a minimal project, I'm still trying to figure how to fix this (maybe) regression. But looks like the dirty check has been enforced in dev. mode from 4.2.x. But the changelog is not clear about that. Any tip welcome.

@jingglang
Copy link

jingglang commented Jun 17, 2017

I get the same problem after upgrading from 4.1.3 to 4.2.3. Using setTimeout fixes the problem.

from:

PanictUtil.getRequestObservable().subscribe(data => this.requesting = data);

to:

PanictUtil.getRequestObservable().subscribe(data => setTimeout(() => this.requesting = data, 0));

@tytskyi
Copy link

tytskyi commented Jun 17, 2017

@jingglang could you please try to reproduce the problem on http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5 with as minimal code as possible?

@KevinRattan
Copy link

I'm getting the same issue - upgraded from 2.4 to 4.2, and some of my hide/show logic is now broken. It's accordion-style logic, where visibility depends on comparing the current panel with an observable that holds which panel should be visible (amongst other things). The component uses @select to get the observable, and it's bound with | async in the template - worked like a charm in 2.4, now get the ExpressionChangedAfterItHasBeenCheckedError and the panels don't hide and show on first click, only on second.

@ver-1000000
Copy link

I get same issue when updated 4.1.3 to 4.2.2.

But, I found workaround.
Injection of ChangeDetectionRef, and call detectionChanges() function at error point.

constructor(private cdr: ChangeDetectionRef) {
}

onChange(): void {
  this.val += 1; // <- get error after
  this.cdr.detectionChanges();
}

@MarkPerryBV
Copy link

I think this is an issue with the follow change made in 4.2.

#16592

to resolve this issue: #14321

Do you have any content inside a ng-content tag?

I have the same issue with a form inside a ng-content tag which now errors with the ContentChangedAfter error.

@mmenik
Copy link

mmenik commented Jun 19, 2017

I get same issue when updated 4.1.3 to 4.2.0 or higher

I attach a little project for reproduce the bug

app-error.zip

@KevinRattan
Copy link

Following up, I downgraded to 4.1.3 (as advised above) and the error goes away, so whatever the conflict is, it's specific to 4.2. I haven't got time to put together a plunkr this week (or next), but it seems to be related to a single action updating two separate observables on the store, each of which has an impact on the UI. One of the UI updates happens, the other causes the exception.

@altreze
Copy link
Author

altreze commented Jun 19, 2017

Hmmm,
Definitely rollback to version 4.1.3 solves the issue and also apply timeout as shown by @jingglang.

@umens
Copy link

umens commented Jun 19, 2017

hi @tytskyi. I made a plunkr http://plnkr.co/edit/XAxNoV5UcEJOvsAbeLHT for this issue. hope it will help.

the workaround given by @jingglang works (for me) on 4.2.0 or higher
OR also changing the emitting event of the child into its constructor don't raise the error anymore

@alexzuza
Copy link
Contributor

@umens You update parent component after it was checked therefore you don't keep unidirection data flow

@umens
Copy link

umens commented Jun 19, 2017

@alexzuza how can I achieve that ? I've always done it this way without the error. Why putting the SetTimout doesn't raise the error anymore. (I updated the plunkr) I doubt it's interfering with the lifecycle or is it ?

@tytskyi
Copy link

tytskyi commented Jun 19, 2017

hi @umens thank you for your time. The problem is what @alexzuza says: you update parent component field after it was checked. Your code is roughly equivalent to this: http://plnkr.co/edit/ksOdACtXScZKw3VRAYTm?p=preview (notice i removed service, to reduce code).
Why it worked? Probably by accident old version of angular or Rxjs had bug here. Could you please tell which versions was used? Or even put into working plunker?

Why putting the SetTimout doesn't raise the error anymore.

Because you change field asynchronously, which respects one-way data flow.

Lets consider it from the opposite: Why error is raised? First check of Change Detection goes from up to down. It checks app.toolsConfig value bound to template. Then it renders <child-cmp> which synchronously updates app.toolsConfig. Rendering is done. Now it runs second (dev mode only) check of Change Detection to ensure that application state is stable. But app.toolsConfig before rendering child does not equal app.toolsConfig after. All this happens during single "turn", "cycle" of Change Detection.

@umens
Copy link

umens commented Jun 19, 2017

ok. thank you for the detailed answer. I couldn't find informations about when the child component lifecycle happens.
for the previous "working" version I used:
@angular/* : 4.1.1 (except compiler-cli -> 4.1.0)
rxjs: 5.3.1

@tytskyi
Copy link

tytskyi commented Jun 19, 2017

@umens here is reproduced error with the old versions you mentioned: http://plnkr.co/edit/kfoKmigXzFXwOGb2wyT1?p=preview. It throws, so probably some 3rd party dependency affected behavior or not complete reproduction instruction?

@umens
Copy link

umens commented Jun 19, 2017

can't tell.
here is my yarn.lock if you are interested in going further : https://pastebin.com/msARLta1
but i don't know what can be different

@saverett
Copy link

I'm seeing similar "ExpressionChanged..." errors after switching from 4.1.2 to 4.2.3.

In my case, I have two components that interact via a service. The service is provided via my main module, and ComponentA is created before ComponentB.

In 4.1.2, the following syntax worked just fine without errors:

ComponentA Template:

<ul *ngIf="myService.showList">
...

ComponentB Class:

ngOnInit() {
  this.myService.showList = false; //starts as true in the service
  ...
}

In 4.2.3, I have to modify ComponentA's template as follows to avoid the "ExpressionChanged..." error:

ComponentA Template:

<ul [hidden]="!myService.showList">
...

I'm still trying to figure out why this has only just become an issue, and why the switch from *ngIf to [hidden] works.

@victornoel
Copy link
Contributor

I have the same problem, and it happens mostly when I'm using an async pipe like this:
<div>{{ (obs$ |async).someProperty }}</div>
If I use it like this:

<div *ngIf="obs$ | async as o">
  <div>{{ o.someProperty }}</div>
</div>

Then the error disappear, so I'm not so sure it is only because a lot of people made some mistake that wasn't detected before: I think a bug was introduced in 4.2…

@cobaltutby
Copy link

The same problem for me. The issue looks like come from @angular/router

@KevinRattan
Copy link

I've also come to the conclusion it's related to the router. Tried to post as much yesterday, but it looks like the post failed. I investigated further and simplified the code to see if I could track down the error. The following code works perfectly when the ultimate source of the action is a click on a panel header, but fails with the ExpressionChanged error when triggered via routing.

<span class="glyphicon pull-right" [ngClass]="{'glyphicon-chevron-up' : (ui$ | async)?.visiblePanels[VisiblePanel.IngredientTypes], 'glyphicon-chevron-down' : !((ui$ | async)?.visiblePanels[VisiblePanel.IngredientTypes])}"></span>

@acidghost
Copy link

This helped me solving the problem! You have to explicitly trigger the change in the parent.

@mpalourdio
Copy link

mpalourdio commented Jun 26, 2017

Like @acidghost, I could solve this problem by running ChangeDetectorRef#detectChanges in AfterViewInit from my parent component.

@ghetolay
Copy link
Contributor

If you have plunkr repro I'm willing to check on it because we had some similar complains on gitter and it always ended up being an user problem.

Can't be sure if a bug was introduced in 4.2 or if the bug was rather the lack of error on < 4.2 and it got resolved on 4.2.

@aks4it
Copy link

aks4it commented Jun 27, 2017

Error: ExpressionChangedAfterItHasBeenCheckedError
solution:
componentRef.changeDetectorRef.detectChanges();

Below is a piece from my code (dynamically generated components):
componentRef = viewContainerRef.createComponent(componentFactory); //component got created
(componentRef.instance).data = input_data; //manually changing component data over here
componentRef.changeDetectorRef.detectChanges(); // it will result in change detection

For simple components just google how to access "componentRef" and then execute
componentRef.changeDetectorRef.detectChanges();
after setting/changing component data/model.

Solution 2:
I was again getting this error while opening a dialog "this.dialog.open(DialogComponent)".
so putting this open dialog code inside a function and then applying setTimeout() on that function solved the issue.
ex:
openDialog() {
let dialogRef = this.dialog.open(DialogComponent);
}
ngOnInit(): void {
setTimeout(() => this.openDialog(), 0);
}
though it seems like hack.. works!!!

@Martin-Luft
Copy link

Martin-Luft commented Jun 28, 2017

Since 4.2 I have the same problem and no solution or workaround worked for me :( I create a component dynamically with a component factory in a ngAfterViewInit method. This component uses a directive which has an @Input() field. I bind this field to a static string. It seems that the @Input() field of the directive is undefined until the view of the component was build and checked and only then initialized with the static string.

https://plnkr.co/edit/E7wBQXm8CVnYypuUrPZd?p=info

staticComponent.ts

export class StaticComponent implements AfterViewInit {
  @ViewChild('dynamicContent', {read: ViewContainerRef})
  dynamicContent: ViewContainerRef;

  constructor(private componentFactoryResolver: ComponentFactoryResolver) {}

  ngAfterViewInit(): void {
    const componentFactory: ComponentFactory<DynamicComponent> = this.componentFactoryResolver.resolveComponentFactory(DynamicComponent);
    this.dynamicContent.createComponent(componentFactory);
  }
}

test.directive.ts

@Directive({
  selector: '[testDirective]'
})
export class TestDirective {
  @Input()
  testDirective: string;
}

dynamic.component.ts

@Component({
  selector: 'dynamic-component',
  template: `<div [testDirective]="'test'">XXX</div>`
})
export class DynamicComponent {
}

ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: 'test'. It seems like the view has been created after its parent and its children have been dirty checked. Has it been created in a change detection hook ?

@nhhockeyplayer
Copy link

I am seeing that when interrogating the redux state its guaranteed to deliver but on startup like it when put in ngOnInit as opposed to constructors

@JavierFuentes
Copy link

@ktabarez Thanks a lot!

Your async ... await in subscribe solution works.

@diegofrata
Copy link

@ktabarez Your solution is essentially doing the same as a setTimeout({}, 0) but it feels more elegant. Quite like it! It also made me realize that anything can be awaited (pretty much like C# await Task.FromResult(tickedMessage)), so cheers for that!

@daddyschmack
Copy link

daddyschmack commented May 29, 2018

So, question: why should we have to do an async/await on a calculation of a property on an observable that is getting data from the back end that might be changing frequently? What about ngClass makes this necessary? I get this while using an {observable | async} pipe. So the async/await isn't the best apprach for me. (or the async pipe isn't the best approach)

@roelzkie15
Copy link

I have this issue where my parent component have multiple children components. One of the child component has an Input where it need a data from the parent component property and the property gets its data from a Observable/Promise (tried both) .

parent.component.html

<mat-tab-group>
   <mat-tab>
      <app-child-1></app-child-1>
   </mat-tab>
   <mat-tab>
      <app-child-2></app-child-2>
   </mat-tab>
   <mat-tab>
      <app-child-3 [datasource]="data"></app-child-3>
   </mat-tab>
</mat-tab-group>

parent.component.ts

// ... omitted...
data: any;

ngOnInit() {
   this.service1.getMethod(some_id).then(data => this.data = data);
}

I came across every solutions found in the thread. But nothing works in my case. Fortunately as some hinted that this is an issue due to manipulating a DOM, like putting an expression to (ngIf) as example.

So in order to avoid this I try experimenting with ng-template, ng-container and ngTemplateOutlet.

<ng-container *ngTemplateOutlet="data ? content : loading"></ng-container>

<ng-template #content>
   <mat-tab-group>
      <mat-tab>
         <app-child-1></app-child-1>
      </mat-tab>
      <mat-tab>
         <app-child-2></app-child-2>
      </mat-tab>
      <mat-tab>
         <app-child-3 [datasource]="data"></app-child-3>
      </mat-tab>
   </mat-tab-group>
</ng-template>

<ng-template #loading>Loading your component. Please wait</ng-template>

I was able to solve this without using ChangeDetectorRef or setTimeout()

@julianosouzanh
Copy link

julianosouzanh commented Jun 21, 2018

For me, this error occurred when showing / hiding field according to the condition, I was able to solve it after applying the values ​​of the fields setting:

this.form.get ('field').updateValueAndValidity();

@jordangotbaum
Copy link

jordangotbaum commented Jul 18, 2018

I've been trying to do this with a mat-dialog that opens on init, and I have literally tried every solution I've seen on this thread (set timeout, promise resolve, observable pipe, after view init, after content init) and combinations therein. Still doesn't work. Then I saw @Splaktar post about how this violates one way data flow, so I literally made a flag in ngrx saying to open the dialog and subscribed to that, which is false initially and I'm still getting this error. What on earth do I do? Is there literally no way to open a modal dialog without attaching it to some completely separate event?

In on init:
this.store.pipe( select(getShouldLogin), ).subscribe((shouldLogin: boolean) => { if (shouldLogin) { this.openLoginDialog(); } }); this.checkUserId();

Auxiliary functions:
checkUserId() { const id = this.cookies.getUserId(); if (!id || id === 'undefined') { this.connApi.setShouldLogin(true); } }
openLoginDialog() { const dialogRef = this.dialog.open(LoginDialogComponent, { width: '400px', height: '300px', }); dialogRef.afterClosed().subscribe(result => this.handleLogin(result)); }

Using angular 6.0.0-rc.1

@paxapy
Copy link

paxapy commented Jul 20, 2018

i'm using angular with ngrx and got this error after submitting a form.
fixed by setting
ChangeDetectionStrategy.OnPush
on form component

@DmitryEfimenko
Copy link

still having the issue. tried all sorts of workarounds. setTimeout works first time, but subsequent triggering of dialog.open result in the same error. Any status on this?

@akankshahardwani26
Copy link

I am facing the same issue. Tried everything but it seems this issue arises on one way data flow.
Here I am trying to update selectedwoids. where the problem solves but the same error arises.

        <p-row> <p-column class="text-left" footer="{{selectedWoids.length}} {{getWoidString(selectedWoids.length)}} selected out of {{getTotalCounts()}} {{getWoidString(getTotalCounts())}}" colspan="11"></p-column> </p-row>

private setSelectedWoidsCount(){
if(this.isSelectAllWoids) {
this.selectedWoids = this.allWoids;
}
}

@IAMRogerXi
Copy link

Because you break the rendering pipeline of Angular...

@alokkarma
Copy link

I'm seeing similar "ExpressionChanged..." errors after switching from 4.1.2 to 4.2.3.

In my case, I have two components that interact via a service. The service is provided via my main module, and ComponentA is created before ComponentB.

In 4.1.2, the following syntax worked just fine without errors:

ComponentA Template:

<ul *ngIf="myService.showList">
...

ComponentB Class:

ngOnInit() {
  this.myService.showList = false; //starts as true in the service
  ...
}

In 4.2.3, I have to modify ComponentA's template as follows to avoid the "ExpressionChanged..." error:

ComponentA Template:

<ul [hidden]="!myService.showList">
...

I'm still trying to figure out why this has only just become an issue, and why the switch from *ngIf to [hidden] works.

Did you find it how it worked when switching from ngIf to [hidden]

@robeverett
Copy link

Is there a solution for this issue in Angular 6 yet ??

@mlc-mlapis
Copy link
Contributor

mlc-mlapis commented Sep 25, 2018

@alokkarma ... too old ... you should migrate it to Angular 6 or 5 at least. But your example code looks clear ... you are changing the service property in the child component B, and it affects some behavior in the child component A. Both children components are placed in the same parent, so all three are processed all together in relation in one CD cycle. What do you expect?

The fact that the same case was working in version 4.1.2 is related to some bugs which were eliminated in later versions, including 4.2.3.

@robeverett
Copy link

I AM using Angular 6. I am using a MessageService to communicate between my authentication component and my app component to show different nav bar content if user is logged in (or not)

It works fine apart from this error!
I've tried other solutions such as EventEmitter but it doesn't work! or I get the same error.

@mlc-mlapis
Copy link
Contributor

@dotNetAthlete ... show a simple reproduction demo on Stackblitz. Talking about it without the real code is hard.

@alignsoft
Copy link

@dotNetAthlete I'm using the same basic mechanism where I have a state service that contains a page title as a behaviour subject (asObservable) in the main container component, and child components loaded into it which set the appropriate page title in the state service for the parent container to observe and display.

There was always an 'Expression changed' error when the container initialized the value and then the child component immediately updated it, so I do this now in the parent/container:

    this.stateSvc.currentPageTitle$
      .subscribe(
        (async (pageTitle) => {
          this.pageTitle = await pageTitle;
        }))

Where the state service has this:

  // Store
  private currentPageTitleStore = new BehaviorSubject<string>("");

  // Getter (as Observable)
  public currentPageTitle$ = this.currentPageTitleStore.asObservable();

  // Setter
  public setCurrentPageTitle(pageTitle: string) {
    this.currentPageTitleStore.next(pageTitle);
  }

This eliminates the issue. This solution is based on some helpful feedback from others in this thread.

@robeverett
Copy link

@alignsoft - beautiful solution - issue fixed in Angular 6 app.

@Exlord
Copy link

Exlord commented Nov 3, 2018

I am seeing this on page reload on angular 7 now ...
I am setting a child components value on parents ngAfterViewInit
Had to do this on the child component

  public activate() {
    setTimeout(() => this.active = true);
  }

@zak905
Copy link

zak905 commented Nov 3, 2018

I found the solution in this issue thread #10762
After I used the AfterContentInit life cycle hook, the error goes away.

@MaheshSasidharan
Copy link

@alignsoft , just adding to your change, since we are using a BehavioralSubject instead of a Subject,

I refined it as following:

\\ Component
export class AppComponent implements OnInit {
  isLoading: Boolean;

  constructor(private loaderService: LoaderService) { }

  ngOnInit(){
    this.loaderService.isLoading.subscribe(async data => {
      this.isLoading = await data;
    });
  }
}
\\ Service
@Injectable({
  providedIn: 'root'
})
export class LoaderService {
  // A BehaviorSubject is an Observable with a default value
  public isLoading: BehaviorSubject<Boolean> = new BehaviorSubject(false);
  constructor() {}
}
\\ Any other component or in my case, an interceptor that has the LoaderService injected
this.loaderService.isLoading.next(true);

But I agree with @daddyschmack , I would like to get any resource that points to info regarding withotu any of the above mentioned fixes (cdr.detectionChanges() or async await or aftercontentchecked), why [hidden] attribute works and not *ngIf

@fromage9747
Copy link

@acidghost Thank you for that link, it solved my problem. I actually had given up on trying to fix it in one area of my application but when it started happening again in a new area...

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 15, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: core Issues related to the framework runtime regression Indicates than the issue relates to something that worked in a previous version type: bug/fix
Projects
None yet
Development

No branches or pull requests