Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ExpressionChangedAfterItHasBeenCheckedError from Angular

Tags:

This is continuation to my original issue

stackoverflow.com/questions/44596418/angular-throws-expressionchangedafterithasbeencheckederror-with-textarea

which is still unresolved. I recreated the orignal plunkr to simulate the actual project and found it is nothing related to the textarea.

When I go to details page by clicking on a item from the list,the exception ExpressionChangedAfterItHasBeenCheckedError is thrown. This happens only when the CodeView of src/detailitems.ts has more than one element in the array. The CodeView items defines the fields in detail form.

import { FormBase } from './formbase'
import { ItemBase, TextboxItemBase } from './itembase'

export class CodeView extends FormBase {

    static getItems() :ItemBase[] {

    let items: ItemBase[] = [

        new TextboxItemBase(
            {
                key: 'id',
                label: 'ID',
                value: '',
                required: true,
                enabled: false,
                readOnly: true,
                size: 36
            }
        )
,
        new TextboxItemBase(
            {
                key: 'description',
                label: 'Description',
                required: true,
                size: 20

            }
        )            
    ];

    return items;
}

}

If I modify the code so that the CodeView has only 1 item, then the exception goes away.

Exception Plunkr

No Exception Plunkr (Just one item in detailitems)

like image 579
padhu Avatar asked Jul 10 '17 21:07

padhu


1 Answers

Your error comes from A-Item component more precisely from this node

<div [formGroup]="form"

when you have such binding angular will automatically create NgControlStatusGroup directive that sets CSS classes based on control status (valid/invalid/dirty/etc).

export const ngControlStatusHost = {
  '[class.ng-untouched]': 'ngClassUntouched',
  '[class.ng-touched]': 'ngClassTouched',
  '[class.ng-pristine]': 'ngClassPristine',
  '[class.ng-dirty]': 'ngClassDirty',
  '[class.ng-valid]': 'ngClassValid', // you get error in this binding
  '[class.ng-invalid]': 'ngClassInvalid',
  '[class.ng-pending]': 'ngClassPending',
};

At first time you don't provide any value for your controls. That's why valid property for your form is false. Then you populate it by using ngModel during change detection cycle and form becames valid. Valid property for form is calculated from all your controls. If you have only one control then valid property will depend only on one control and A-Item component won't raise error.

I would prepare data before rendering.

You can open form.component.ts and find the following code

this.formItems.forEach(item => {
   group[item.key] = item.BuildControl();
   this.items.push(new formObjectItem(item, this.getData(item.key)));
});

then you need to patch data for form

this.formItems.forEach(item => {
   group[item.key] = item.BuildControl();
   this.items.push(new formObjectItem(item, this.getData(item.key)));
   group[item.key].patchValue(this.getData(item.key)); // <== this line
});

or

this.formItems.forEach(item => {
   group[item.key] = item.BuildControl();
   const value = this.getData(item.key);
   this.items.push(new formObjectItem(item, value));
   group[item.key].patchValue(value);
});

This way your form will be synchronized with your value and will have correct status.

In this case you can also remove itemValue and ngModel from A-Item component since reactive model will do all work.

Forked Plunker

You don't get such error

One more tips:

import * as Rx from 'rxjs/Rx'

you will ship all rxjs library to your bundle

like image 150
yurzui Avatar answered Oct 12 '22 19:10

yurzui