Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid ExpressionChangedAfterItHasBeenCheckedError when using [(ngModel)] binding on textarea

I have created a component that represents the form for modifying the details of an object. The object exists in app.component.ts:

export class AppComponent {
  selectedItem: Item;
}

It is passed in via two-way binding into the component from app.component.html like so:

<item-details [(item)]="selectedItem"></item-details>

Within the component, the individual fields of the Item are bound to input controls, to allow the user to update the data, e.g.:

<mat-form-field class=name>
  <input matInput [(ngModel)]="item.name" value="{{item.name}}" required placeholder="Name">
  <mat-error>Item name can not be left blank</mat-error>
</mat-form-field>

Everything works great until I get to the textarea:

<mat-form-field class="full-width">
  <textarea id=description matInput [(ngModel)]="item.description" placeholder="Description">{{item.description}}</textarea>
</mat-form-field>        

It WORKS, but it throws an exception:

ExpressionChangedAfterItHasBeenCheckedError

The error is not directly tied to the <textarea>, as it says that the value went from false to true and as such appears to be related to the valid property on the form, as hinted at here.

Interestingly, I can avoid the error by modifying the contents of the <textarea></textarea> such as by putting a space after the contents:

<textarea ...>{{item.description}} </textarea>

But that only works if item.description is not null. When it is null then I get the error again.

I'm triggering the change to selectedItem from another child component, which also has bi-directional binding on selectedItem. When the user selects an item, the new Item flows up to the app, and back down to the detail component.

I have read Everything you need to know about the 'ExpressionChangedAfterItHasBeenCheckedError' error article. To quote the article "I don't recommend using them but rather redesign your application".

Great! How? How do I structure things so that control A is used to select an Item, and control B is used to edit it? What is the right way for controls A and B talk to one another without triggering this error?

like image 488
John Arrowwood Avatar asked Oct 24 '17 20:10

John Arrowwood


2 Answers

This error drove me absolutely crazy, and it only revealed itself after upgrading to Angular 5. In my case, I am not using [(ngModel)]. I am using the textarea for display purposes only (to get Material look and feel). Nevertheless, this may be helpful for others searching endlessly like I have.

I discovered that if you bind to the [value] property of the textarea instead of using interpolation {{}}, the error goes away.

Instead of: <textarea>{{value}}</textarea>

Do: <textarea [value]="value"></textarea>

You didn't have any problems with <input> because it is a single-tag element and therefore must use property binding and not interpolation. (To be clear, your use of interpolation together with value is only necessary because you are binding to an attribute which is evaluated only once. Bind to [value], which is a property, and you no longer need the {{}}. See the angular docs where this is explained very clearly.)

I suspect that with interpolation, angular sets the form as false on the first digest cycle, and on the second digest as true when it recognizes the value. With [value] property binding, it recognizes the value on the first digest.

In any case, it works.

like image 195
andreisrob Avatar answered Oct 11 '22 14:10

andreisrob


If you are using ngModel then {{item.description}} is useless, should be enough:

<mat-form-field class="full-width">
   <textarea id=description matInput [(ngModel)]="item.description" placeholder="Description"></textarea>
</mat-form-field>
like image 32
kemsky Avatar answered Oct 11 '22 14:10

kemsky