6

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)

padhu
  • 155
  • 3
  • 11
  • the first plunker doesn't work. to understand why this error happens read [Everything you need to know about the `ExpressionChangedAfterItHasBeenCheckedError` error](https://hackernoon.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4) – Max Koretskyi Jul 11 '17 at 04:57

1 Answers1

4

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

yurzui
  • 205,937
  • 32
  • 433
  • 399
  • Thank you. When I was debugging, the view.oldValues[def.bindingIndex + bindingIdx] in core.umd.js pointed to the "valid" flag was causing the issue. So for test purpose, I tried returning true always for valid() call but that did not help neither removing the class for the A-Item so I moved on. When I am not setting value to the control when the form has just 1 field, why would it trigger this exception only when the form has more than one field? – padhu Jul 11 '17 at 15:14
  • 1
    When it has only one field it becames stable during `A-Item` template checking. But if you have several fields each of fields doesn't know anything about other. Angular checks first field and since form consists of several controls the valid state for form remains `false` after checking current field view. Then angular moves to check second field and fills value for second control so form becames valid. And finally angular runs checkNoChanges and checks again form.valid. (false => true) – yurzui Jul 11 '17 at 15:28
  • I was stripping and chopping A-Item component because the exception was showing thrown from there but in reality it is from the A-Form then. You had suggested that itemValue and ngModel is not required in A-item but when I removed `@input() itemValue` from A-Item and the`` from A-Form template, the form controls came up empty -> [plunker](http://plnkr.co/edit/elaIGDAzqaHwhjqeaZZ3) – padhu Jul 11 '17 at 16:12
  • 1
    You should also remove `[(ngModel)]` Check this update http://plnkr.co/edit/ankcAjsXjybHi8tB6Q3W?p=preview – yurzui Jul 11 '17 at 16:14
  • got it. I wanted to add that because of the exception earlier, the change detection got messed up too. So when I click "Save" in details and route back to the list page, that page would display a blank list with just the headers and also the drop down which should get enabled in List page remained disabled. Now all that is resolved after resolving the exception. Thank you again. – padhu Jul 12 '17 at 01:19