robert
robert

Reputation: 857

ExpressionChangedAfterItHasBeenCheckedError using push strategy when lazy loading route and async pipe

as soon as I switch from module with component 1 to module with component 2 I got the following error:

ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'ngIf: [object Object]'. Current value: 'ngIf: [object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object]'

Both modules use

ChangeDetectionStrategy.OnPush

When I subscribe my own, everything is fine:

this.bookings$.pipe(takeUntil(this._destroy$)).subscribe(res => console.log(res));

But as soon as I use async pipe for subscription, change detection breaks:

<ion-list *ngIf="bookings$ | async as bookings; else loading">
</ion-list>

The init of module/component 2 is faster then the destroy of component 1 when changing routes. So component 1 get the changes (filteredBookings) initiate by component 2 date change before detroy. But that shouldn't be a problem?

service:

constructor(
  private afs: AngularFirestore
) {
  this.bookingsCollection = afs.collection<Booking>('bookings');
  this.bookingsCollection
    .snapshotChanges()
    .pipe(
      takeUntil(this._destroy$),
      map(actions => {
        return actions.map(a => {
          const data = a.payload.doc.data() as Booking;
          const id = a.payload.doc.id;
          return {id, ...data};
        });
      })
    )
    .subscribe((bookings: Booking[]) => {
      this._bookings.next([...bookings]);
      this.refreshFilteredBookings();
    });
  this.bookings$ = this._bookings
    .asObservable()
    .pipe(
      filter(bookings => bookings !== null)
    );
  this.filteredBookings$ = this._filteredBookings
    .asObservable()
    .pipe(
      filter(bookings => bookings !== null),
    );

}

private refreshFilteredBookings(): void {
  const bookings: Booking[] = JSON.parse(JSON.stringify(this._bookings.value));
  const filteredBookings: Booking[] = bookings.filter(booking =>
    moment(booking.date).isBetween(this.minDate, this.maxDate, null, '[]')
  );
  this._filteredBookings.next(filteredBookings);
}

setDate(date: Moment, type: string) {
  this.minDate = date.startOf(type as unitOfTime.StartOf).format();
  this.maxDate = date.endOf(type as unitOfTime.StartOf).format();
  if (this._bookings.value) {
    this.refreshFilteredBookings();
  }
}

component 1:

ngOnInit() {
   this.bookings$ = this._bookingService.filteredBookings$;
}

component 2:

ngOnInit() {
   this.view = 'month';
   this.date = moment().format();
   this.onDateChange();
}

onDateChange() {
   const m = moment(this.date);
   this._bookingService.setDate(m, this.view);
}

Upvotes: 2

Views: 952

Answers (1)

Vo Xuan Hoan
Vo Xuan Hoan

Reputation: 121

Are you using Angular 6?

The problem may be in lazy loading modules, It does not destroy the current component (component 1) while navigating to another component (component 2). so asycnPipe from Component 1 does not unsubscribe.

While component 2 is initializing, It changed the shared service's value, Component 1 is subscribing from shared service, and it's trying to re-render the view and caused this problem.

I think we can workaround by subscribing the observable from component and pass the value to view but this is not a good way.

You can check other way at here


Update: In my case, I change changeDetectionStrategy from Default to OnPush it solved my problem.

Upvotes: 2

Related Questions