问题
The following code will generate two inputs
with same value bye
(instead of hello
and bye
). It would be great if someone could (theoretically) explain this behaviour and tell the exact cause.
<form>
<div *ngFor="let item of ['hello', 'bye'];">
<input name="name" [(ngModel)]="item">
</div>
</form>
Edit: To explain my question better:
The reason couldn't be that because they are bind to the same object, they will have identical value. If that is so the following case would have the same value for both of inputs, which is obviously not the case.
app.component.html
<form>
<div *ngFor="let item of arr;">
<input name="name" [(ngModel)]="item">
</div>
</form>
app.component.ts
arr = [1,4]
ngOnInit(){
setTimeout(()=>{
this.arr[1] = 5;
});
}
Please note: I think I have explained my question properly and also why I think @DeborahK's solution doesn't seem fit to me. I am looking for a reason for such behavior. And not the workaround. Also, I know that changing name
in each input would make it work fine. So please stop suggesting that.
回答1:
Name attribute sould be unique
<form>
<div *ngFor="let item of ['hello', 'bye'];let i =index">
<input name="{{i}}" [(ngModel)]="item">
</div>
</form>
回答2:
Here is some further explanation as to why the answer here is to have a unique name. And this solution is not a work around. It is just the way it works.
When you use template-driven forms, which you are when you use ngModel
, then Angular automatically builds a data structure to hold all of the form information. This includes state information (dirty, touched, etc) and the form values. It holds this information based on the CONTROL NAME!
So if your names are the same, they are in the data structure as ONE ELEMENT and cannot then have two values.
You can view this data structure yourself if you define a template reference variable for the form:
<form #myForm="ngForm">
<div *ngFor="let item of ['hello', 'bye'];">
<input name="name" [(ngModel)]="item">
</div>
<div>{{ myForm.value | json }}</div>
</form>
I just did a stackblitz to demonstrate your array example to show that it is still only one element with one value:
https://stackblitz.com/edit/angular-xjyslr
回答3:
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);
}
来源:https://stackoverflow.com/questions/53785027/ngmodels-unexpected-behavior-inside-angular-form