Stefan Falk
Stefan Falk

Reputation: 25397

Getting ExpressionChangedAfterItHasBeenCheckedError even with async EventEmitter

I'm haunted by this exception and I just don't get what the problem here is. I have a FormArray which pushes new items to its controls. These controls are passed down to other child components:

<mat-card *ngFor="let itemFormGroup of formArray.controls">
  <app-item [formGroup]="itemFormGroup">
  </app-item>
</mat-card>

However, I've tried several things but it's just not working. I've now tried to use EventEmitter<any>(true) as well as setTimeout() etc but I'm am not getting rid of this exception

@Output()
addItem = new EventEmitter<any>(true);

onMenuSelected(item) {
  this.addItem.emit({date: this.date, item: item});
}

The parent:

onAddItem(event) {
  const formArray = <FormArray> this.form.get(event.date);
  formArray.push(this._formBuilder.group(event.item));
}

The error:

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

Since I am not setting any property named valid, I assume it's the form itself which changes that property during the cycle and breaks things.

How can I avoid this?


This is how the FormArray gets constructed by the parent. The whole thing is a calendar which is basically a huge FormGroup taking the form

// pseudo code

form: FormGroup {     // Form of the entire calendar
  date1: FormArray [  // First day in the calendar with a list of items
    item1: FormGroup, // An item in the calendar
    item2: FormGroup
  ],
  date2: FormArray [
  ]
}

Here's the logic that builds it:

refreshCalendar() {
  const groups = {};

  this.dates.forEach(d => {
    groups[d] = this._formBuilder.array([]);
  });

  if (Object.keys(groups).length > 0) {
    this.form = this._formBuilder.group(groups);
  }

  this.formValueChange$ = this.form.valueChanges.subscribe(() => {
    setTimeout(() => {this._calendarComponentService.setDirty(this.form.dirty);}, 0);
  });

  // This call is important after resetting in order to propagate the 'dirty' state
  this.form.updateValueAndValidity();

  this.updateItems();
}

updateItems() {
  if (Object.keys(this.items).length > 0) {
    for (const k of Object.keys(this.items)) {
      const formArray = <FormArray> this.form.get(k);
      if (formArray == null) {
        continue;
      }
      for (const item of this.items[k]) {
        formArray.push(this._formBuilder.group(item));
      }
    }
  }
}

Upvotes: 1

Views: 569

Answers (1)

Vinaayakh
Vinaayakh

Reputation: 522

Try injecting ChangeDetectionRef and call detectChanges() after you insert the new item.

Upvotes: 1

Related Questions