Mergasov Mergasov -4 years ago 188
Javascript Question

ngModel cannot detect array changes correctly

The component model:

private SomeArray = [{ key: "Initial" }];


User can add/remove items dynamically:

addField() {
this.SomeArray.push({ key: Math.random().toString() });
}

removeField(index: number) {
this.SomeArray.splice(index, 1);
}


Template markup:

<div class="col-xs-12">
<button (click)="addField()" type="button">Add</button>
</div>

<div *ngFor="let field of SomeArray; let i = index;">
<input [(ngModel)]="field.key" #modelField="ngModel" [name]=" 'SomeArray['+i+'].key' " type="text" class="form-control" required />
<div [hidden]="modelField.pristine || !(modelField.errors && modelField.errors.required)" class="alert alert-danger">
Required error
</div>

<button (click)="removeField(i)" class="btn btn-danger">Remove</button>
</div>


This works untill user removes any item from
SomeArray
. If I add some two items initially:
enter image description here

and remove the one with 1 index:

enter image description here

then after adding another item Angular treat it as item has both 0 and 1 index (the new item "occupies" both two inputs):

enter image description here

(item with key 0.1345... is not displayed)

It's worth to noting items of
SomeArray
are as expected, but data binding fails. What can be the reason of it?

Update: Thanks to the comments of @Stefan Svrkota and @AJT_82 it's known for me the issue can be resolved by adding
[ngModelOptions]="{standalone: true}"
to the needed input. But I couldn't stop thinking about the reason of the issue in my cause, without setting
standalone
option (there is unique value for each name attribute so it's excepted nothing wrong here).

Finally I have found that behavior occurs when input elements are into
<form>
tag only - Plunker sample here (enclosing of template with form tag is the reason that issue).

Any ideas of this behavior?

Answer Source

The reason why it happens is ngFor mixes name properties when you delete some item.

When you use ngModel inside form each ngModel control will be added to form controls collection.

Let's see what happens if we have added three items and clicked on Remove the second

1) Step1 - SomeArray[1].key exists in collection controls enter image description here

2) Step2 - SomeArray[1].key has been removed from controls collection

enter image description here

3) Step3 - Html looks like

enter image description here

4) Step4 We are adding a new item

enter image description here

So formGroup returns existing item.

How we can solve it?

1) Don't wrap out controls in form tag

2) Add ngNoForm attribute to form

<form ngNoForm>

3) Use

[ngModelOptions]="{standalone: true}

With all three solutions above:

  • We can remove [name] property binding

  • We can't use the built in Form group validation

4) Use trackBy for ngFor

template.html

<div *ngFor="let field of SomeArray; let i = index; trackBy: trackByFn">

component.ts

trackByFn(i: number) {
  return i;
}

Plunker Example

This way our built in form will work properly

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download