0

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>

enter image description here

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;
    });
  }

enter image description here

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.

HDJEMAI
  • 9,436
  • 46
  • 67
  • 93
dasfdsa
  • 7,102
  • 8
  • 51
  • 93
  • 3
    They have the same name so they are tied to the same item in the form's model. – DeborahK Dec 14 '18 at 18:24
  • @DeborahK Thanks for your comment. Can you please look at my edit. Does that align well with your explanation? – dasfdsa Dec 14 '18 at 18:37
  • Man, change the name property for each input. – rcanpahali Dec 14 '18 at 19:22
  • It is not a work around. The form model uses the name to map to its internal data structures. If you don't use a different name, it won't recognize it as a different value. – DeborahK Dec 14 '18 at 20:01

3 Answers3

3

Name attribute sould be unique

 <form>
      <div *ngFor="let item of ['hello', 'bye'];let i =index">
        <input  name="{{i}}" [(ngModel)]="item">
      </div>
    </form>
Chellappan வ
  • 23,645
  • 3
  • 29
  • 60
  • sorry but I didn't ask for workarounds. I know that very well and its too simple. I asked for a theoretical explanation for that behavior. – dasfdsa Dec 14 '18 at 18:27
  • If you have multiple input element then the name attribute associated with the input element should be unique – Chellappan வ Dec 14 '18 at 18:31
  • 1
    @dasfdsa you already had the explanation from the comment of Deborah. Read the documentation: https://angular.io/api/forms/NgModel#description. NgModel binds the input to a FormControl, registered in the FormGroup under its name. So if you have two inputs with the same name, they share the same FormControl. – JB Nizet Dec 14 '18 at 18:33
  • @JBNizet Can you please see my edit. Thanks for the link. – dasfdsa Dec 14 '18 at 18:38
  • @dasfdsa I think that's bcoz array is non primitive type – Chellappan வ Dec 14 '18 at 18:51
  • If you are referring to change detection not running over array, thats not the case here. – dasfdsa Dec 14 '18 at 19:40
  • No what i am saying is array is pass by reference due to that the second value is changing – Chellappan வ Dec 15 '18 at 03:56
0

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.

enter image description here

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

DeborahK
  • 57,520
  • 12
  • 104
  • 129
  • Thanks a lot for your efforts. I get your reasoning. _if your names are the same, they are in the data structure as ONE ELEMENT and cannot then have two values_ . Both inputs have same source of truth (form control). I get that. But when form controls are changed those changed aren't automatically reflected in form, as we know. And in my example, both the inputs are having the same value. – dasfdsa Dec 14 '18 at 20:56
  • So as an example, if I change form control using some methods those change wont be reflected in the form unless I manually update the form. [stackblitz](https://stackblitz.com/edit/angular-wxakux?file=src%2Fapp%2Fapp.component.ts) – dasfdsa Dec 14 '18 at 21:03
  • While referring to _workaround_ I was referring to other answer. But I feel people are thinking its too obvious while its absolutely not (see my commented code in above stackblitz) – dasfdsa Dec 14 '18 at 21:06
  • AFAIK, you cannot update the Form Model directly with this syntax: `form['controls']['name']['value']='x';` That is why that doesn't work. – DeborahK Dec 14 '18 at 21:08
0

There seems to be a combination of two problems in your code sample:

  1. The two inputs have the same name, which causes them to share the same FormControl
  2. 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:

  1. Give each input a unique name
  2. Prevent input element destruction/creation with a trackBy method (by index)
  3. 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);
}
ConnorsFan
  • 70,558
  • 13
  • 122
  • 146
  • My biggest pain point is, **form doesn't automatically read from FormControl**. Form's input will be in sync with form control only if you explicitly call patchValue(). My point is, even though both input "belong" same FormControl, their value will always be tied to arr[i] because of `[(ngModel)]`. To repeat, form doesn't automatically read from form control, also the value of both inputs, in no way, tied to the value of FormControl. – dasfdsa Dec 15 '18 at 19:51
  • "To illustrate the last point, you can force the two inputs to be recreated on change detection by modifying both of them in code". This makes sense but its bring up a bigger question: When the input is recreated why doesn't it take its value from `FromControl` rather than `arr[i]` as per `[(ngModel)]`. – dasfdsa Dec 15 '18 at 19:58
  • My understanding is that the `ngModel` directive updates the `FormControl`, which in turn updates the `input` element. I added that information to the answer, with links to the Angular documentation. – ConnorsFan Dec 15 '18 at 23:27