There seems to be a combination of two problems in your code sample:
- The two inputs have the same name, which causes them to share the same FormControl
- Each input element is removed and recreated when it is updated on change detection. If the other value was not modified, the corresponding input element is not recreated. This seems to cause a desynchronisation with the FormControl, and we see different values in the two fields.
To illustrate the last point, you can force the two inputs to be recreated on change detection by modifying both of them in code:
changeValues() {
this.arr[0] = 2;
this.arr[1] = 3;
}
You can see in this stackblitz that both inputs have the same content after the update.
The destruction/creation of bound input elements in an ngFor
loop can be prevented with the help of a trackBy
method, to track the array elements by their index instead of tracking them by their value. You can see in this stackblitz that the two input elements correctly share the same FormControl.
<div *ngFor="let item of arr; trackBy: trackByFn">
<input name="name" [ngModel]="item">
</div>
trackByFn(index, value) {
return index;
}
In the end, the correct behavior can be obtained with 3 changes to the original code:
- Give each input a unique name
- Prevent input element destruction/creation with a
trackBy
method (by index)
- For two-way binding, bind the array value by its index instead of binding to the loop variable
item
<div *ngFor="let item of arr; let i = index; trackBy: trackByFn">
<input name="name_{{i}}" [(ngModel)]="arr[i]">
</div>
trackByFn(index, value) {
return index;
}
You can see this stackblitz for a demo.
Data flow in template-driven forms (model to view)
The ngModel
directive updates the FormControl
when the bound data is modified, by calling FormControl.setValue
:
ngModel source code:
ngOnChanges(changes: SimpleChanges) {
...
if (isPropertyUpdated(changes, this.viewModel)) {
this._updateValue(this.model);
this.viewModel = this.model;
}
}
private _updateValue(value: any): void {
resolvedPromise.then(
() => { this.control.setValue(value, {emitViewToModelChange: false}); });
}
and you can see that FormControl.patchValue
also calls setValue
:
FormControl source code:
patchValue(value: any, options: {
onlySelf?: boolean,
emitEvent?: boolean,
emitModelToViewChange?: boolean,
emitViewToModelChange?: boolean
} = {}): void {
this.setValue(value, options);
}