Basheer Kharoti
Basheer Kharoti

Reputation: 4302

Expression has changed after it was checked - Event subscription updates

Though I know the reason as to why this ERROR happens, However, I would like to know how to overcome in the following scenario:

I have a generic loader in app.component. For some of the child routes it'll update the loader value on ngOnInit life cycle until records are fetched. Also, I do not want to use resolver as I'm parsing the query params in the component and then passing to the service to fetch the records.

The ERROR is due to the default value of the loader to false and changes to true during the child component life cycle ngOnInit hook.

Please have a look at the following snippet:

// app.component.ts
 viewModel = {
    showLoader: false
};
ngOnInit() {
    this.eventService.on(ApplicationEvents.SHOW_FULL_PAGE_LOADER, (value) => {
        this.viewModel.showLoader = value;
    });
}
// app.component.html
<div class="full-page-loader" *ngIf="viewModel.showLoader"><em class="fa fa-cog fa-spin loader-icon"></em></div>

And following is the snippet from the lazyloaded child component:

ngOnInit() {
    this.loadData();
    this.cart = this.cartService.getCart();
  }

private loadData() {
this.eventService.showFullPageLoader(); // <-- This will emit an event to show the loader 
this.umrahDataService.getServices(UmrahServiceType.Visa).then((resp) => {
  this.visas = resp.map((v) => {
    v.image = '/assets/img/umrah/visa-processing.jpg';
    return v;
  });

  this.eventService.hideFullPageLoader();
  this.actionInProcess = false;
}).catch((er) => {
  this.alertService.alert('SERVER_ERROR');
  this.eventService.hideFullPageLoader();
  this.actionInProcess = false;
});
}

ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'ngIf: false'. Current value: 'ngIf: true'. at viewDebugError (core.js:19629) at expressionChangedAfterItHasBeenCheckedError (core.js:19617) at checkBindingNoChanges (core.js:19833) at checkNoChangesNodeInline (core.js:29533) at checkNoChangesNode (core.js:29522) at debugCheckNoChangesNode (core.js:30126) at debugCheckDirectivesFn (core.js:30054) at Object.eval [as updateDirectives] (AppComponent.html:3) at Object.debugUpdateDirectives [as updateDirectives] (core.js:30043) at checkNoChangesView (core.js:29421)

Although I tried BehaviorSubject with async pipe as well and that didn't help either. Looking forward for the feedbacks. Thanks

Upvotes: 1

Views: 9213

Answers (3)

Abhishek Kumar Pandey
Abhishek Kumar Pandey

Reputation: 449

Angular change detection Process is Synchronous, to avoid this error we can make this as async process, and we can make that code to run outside of Change Detection by doing something like.

ngOnInit(){
  this.ngZone.runOutsideAngular(() => {    
     this.interval = window.setInterval(() => {
      this.eventService.on(ApplicationEvents.SHOW_FULL_PAGE_LOADER, (value) => {
      this.viewModel.showLoader = value;
      });
  }, 1)});
};

Upvotes: 0

Ashokan Sivapragasam
Ashokan Sivapragasam

Reputation: 2199

Whenever you change data after view has been loaded, let Angular watcher to digest the changes. Angular has ChangeDetectorRef service. You can inject this service and force the angular to detect and include the changes in the digest cycle.

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

@Component({
  selector: 'app-your-comp',
  templateUrl: 'your-comp.component.html',
  styleUrls: ['your-comp.component.scss']
})
export class YourCompPage {
    constructor(private changeDetectorRef: ChangeDetectorRef) {}

    ngAfterViewInit() {
        ..any data changes or your flag in this case..
        this.changeDetectorRef.detectChanges();
        // -- OR --
        this.eventSubscription.someEvent(() => {
            ..any data changes or your flag in this case..
            this.changeDetectorRef.detectChanges();
        });
    }
}

P.S. https://indepth.dev/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error/

Upvotes: 3

Yuriy Kravets
Yuriy Kravets

Reputation: 1263

In most cases moving your reactive code to ngAfterViewInit helps. So in your child component instead of:

ngOnInit() {
    this.loadData();
    this.cart = this.cartService.getCart();
}

use

ngAfterViewInit() {
    this.loadData();
    this.cart = this.cartService.getCart();
}

You could also use setTimeout in your app.component.ts, but that is more like a hack rather than a solution.

Upvotes: 1

Related Questions