Search code examples
angularangular-changedetection

Facing "ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked" to show/hide loader


I have a common loader along with router-outlet (to load child components) in parent component (AppComponent). In child component, we change value to show and hide the loader before and after calling an API respectively. Hence this error occurs.

for example this is my loader in app.cpmponent.html:

<p [ngClass]="(overlappingProgress | async) ? 'display-block' : 'display-none'">holaaaaaaaaaaaaaaa</p>

here overlappingProgress is a observable type of variable that is resolved through async pipe.

In child component, I have a form that needs some data from server. so, inside ngOnInit of child component I dispatched some actions(as we are using ngrx) in the following sequence -

  1. showOverlappingProgress() // it makes loading property true in reducer
  2. GetData()
  3. hideOverlappingProgress() // it makes loading property false in reducer

so, in app.component.ts to detect that change, I listen the changes to update show/hide the loader.

this.overlappingProgress = this.store.select(s => s.app.overlappingProgress);

I have some idea that it compares the previous/old value with the value of 1st digest cycle, but the value is changing inside the digest cycle so, while it runs second digest cycle, the value mismatch and thus this error occurs.

I mimic this scenario with a simpler version in stackblitz.

I found some solution that suggested to use setTimeout() or this.cdr.detectChanges().

I personally don't feel setTimeout() is ok. Besides, trigger detectchanges manually may do a lot of extra work as all the child components are suing this loader and every time it will trigger and extra change detection.

So, here I am to get some suggestion on what should be best solution in this scenario.


Solution

  • It has parent child relation. The child component changes the value of parent component that is used inside an expression in parent component.

    To solve the issue, I changed the parent-child structure a bit. I have created a new module called FfLoadingIndicatorComponentModule and it has a component called FfLoadingIndicatorComponent. Inside that component I subscribed the changes it show hide the loader respectively. Finally I import that module in AppModule to use the newly created loading indicator component from app component.

    here the main thing is the separate component and its relation with the app component. lets see the code. code example:

    ff-loading-indicator.component.ts

    @Component({
        selector: 'ff-loading-indicator',
        templateUrl: './ff-loading-indicator.component.html',
        styleUrls: ['./ff-loading-indicator.component.scss']
    })
    export class FfLoadingIndicatorComponent implements OnInit {
        progress$: Observable<boolean>;
        overlappingProgress$: Observable<boolean>;
    
        constructor(private store: Store<State>) { }
        ngOnInit(): void {
            this.progress$ = this.store.select(s => s.app.progress);
            this.overlappingProgress$ = this.store.select(s => s.app.overlappingProgress);
        }
    }
    

    ff-loading-indicator.component.html

    <div [ngClass]="(progress$ | async) ? 'display-block' : 'display-none'">
        <mat-progress-bar class="ff-z-index-above-toolbar ff-general-loading-indicator" mode="indeterminate" color="accent"></mat-progress-bar>
    </div>
    

    and finally use this component in app.component.html.

    app.component.html

    <ff-loading-indicator></ff-loading-indicator>
    

    you can find the changes here in stackblitz.

    Note: FfLoadingIndicatorComponent can be imported directly in AppModule. As I preferred the module approach, I created a separate module and import that in AppModule