2
votes

I am trying to share data between components using the rxjs subject and i've used that data in component

Component.html

<div class="spinner-container"  *ngIf="loading">
      <div class="spinner-item">
          <nx-spinner nxSize="large"></nx-spinner>
      </div>
</div>  

component.ts

ngOnInit(){
    setTimeout(()=>{
      this.commonService.spinnerTrigger.subscribe((trigger)=>{
        this.loading = trigger;
     })
    },100)
}

Here is the error

ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'ngIf: false'. Current value: 'ngIf: true'.

I found a workaround using changedetectref but I don't think its good practice is ther any other way to solve this issue

4
Does it works if you subscribe in a ngAfterViewInit method (without the timeout) ? - Michael Desigaud
Using change detector is the right solution. - Suhas Parameshwara
@MichaelDesigaud nop still the error - iam batman

4 Answers

3
votes

You can manually trigger change detection using the detectChanges() method of the ChangeDetectorRef

Try like this:

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

constructor(private cdr: ChangeDetectorRef) { }

ngOnInit(){
    setTimeout(()=>{
      this.commonService.spinnerTrigger.subscribe((trigger)=>{
        this.loading = trigger;
        if (this.cdr && !(this.cdr as ViewRef).destroyed) {
           this.cdr.detectChanges();
        }
     })
    },100)
}
0
votes

Making the next callback async worked for me once:

this.commonService.spinnerTrigger.subscribe(async (trigger) => {
  this.loading = await trigger;
});

Or adding a zero delay:

this.commonService.spinnerTrigger.pipe(delay(0)).subscribe((trigger) => {
  this.loading = trigger;
});
0
votes

This is an open issue in Github,

Github issue => https://github.com/angular/angular/issues/15634

And they provided a workaround using setTimeout() for now and still there aren't any updates regarding this issue.

And also you can try changeDetector that may solve your issue.

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

constructor(private cdRef:ChangeDetectorRef) {}

ngAfterViewChecked()
{
  this.cdRef.detectChanges();
}
0
votes

I don't see any need here to mess around with change detection / setTimeout (which triggers change detection).

Stackblitz

Use a spinner service which parent and child can use.

spinner.service.ts

@Injectable()
export class SpinnerService {
  private loading = new BehaviorSubject<boolean>(true)
  loading$: Observable<boolean> = this.loading.asObservable()

  setSpinner(bool: boolean) {
    this.loading.next(bool)
  }
}

Example - Component setting spinner

  ngOnInit() {
    this.service.getChildData().pipe(
      // handle any errors
      catchError(err => {
        console.log('Error caught: ', err)
        this.data = err
        return throwError(err)
      }),
      // no matter what set spinner false
      finalize(() => {
        this.spinnerService.setSpinner(false)
      }),
      // subscription clean up
      takeUntil(this.destroyed$) 
    ).subscribe(data => this.data = data)
  }

Example - parent / container displaying spinner

  ngOnInit() {
    this.loading$ = this.spinnerService.loading$
    this.spinnerService.setSpinner(true) // if needed
  }
    <div *ngIf="loading$ | async">
      I am a spinner
    </div>