Angular 2 - 2 Way Binding with NgModel in NgFor
Asked Answered
F

4

75

In Angular 2 how would I get 2 way binding with NgModel within a repeating list using NgFor. Below is my plunkr and code but I get an error.

Plunkr

@Component({
  selector: 'my-app',
  template: `
  <div>
    <div *ngFor="let item of toDos;let index = index;">
      <input [(ngModel)]="item" placeholder="item">
    </div>
    Below Should be binded to above input box
    <div *ngFor="let item of toDos">
      <label>{{item}}</label>
    </div>
  </div>
  `,
  directives: [MdButton, MdInput]
})
export class AppComponent { 
  toDos: string[] =["Todo1","Todo2","Todo3"];
  constructor() {}
  ngOnInit() {
  }
}
Ferri answered 29/10, 2016 at 0:6 Comment(3)
You get an error because you can't do this: ``` <input [(ngModel)]="item" placeholder="item"> ``` You can't bind ng-model to the reference variable "item." I am not sure what you are trying to accomplish. Can you elaborate?Creodont
It is ok I found the solution I need to include trackByIndex then bind to toDos [index] instead. will update plunkr shortly.Ferri
Just post an answer :-)Rode
F
122

After digging around I need to use trackBy on ngFor. Updated plnkr and code below.

Working Plnkr

@Component({
  selector: 'my-app',
  template: `
  <div>
    <div *ngFor="let item of toDos;let index = index;trackBy:trackByIndex;">
      <input [(ngModel)]="toDos[index]" placeholder="item">
    </div>
    Below Should be binded to above input box
    <div *ngFor="let item of toDos">
      <label>{{item}}</label>
    </div>
  </div>
  `,
  directives: [MdButton, MdInput]
})
export class AppComponent { 
  toDos: string[] =["Todo1","Todo2","Todo3"];
  constructor() {}
  ngOnInit() {
  }
  trackByIndex(index: number, obj: any): any {
    return index;
  }
}
Ferri answered 29/10, 2016 at 3:16 Comment(8)
Actually the trackBy does not matter in your code! What matters is [(ngModel)]="toDos[index]" instead of [(ngModel)]="item"Merkel
@Merkel without the trackBy the input loses focus and seems to hang in my experience.Artful
Without the trackBy ngFor re-creates the DOM every time you change a string in your array, so this is totally necessary.Cincinnatus
@Merkel trackBy:trackByIndex; is needed otherwise you lose focus on the input after first keypress.Subastral
In case you have your <input> inside a <form>, angular force you to put a name on the input, in that case when modifying the string array it will display 3 time "Todo3" but no erro will be throw and everything else will be working. You can replace the name by [ngModelOptions]="{standalone: true}" so that you get all 3 differents strings on the inputs.Subastral
Would like to add here that using [attr.name] instead of [name] will break the ngModel as wellAlbarran
Just wanted to comment that this solution also resolved the focus loss when using ngx-color-pickerDrucilladrucy
is there a way to do this but for a dictionary? I would like to track the slider value for each key in a dict..Pantry
W
69

What you have done is not working because of two reasons.

  • You have to use toDos[index] instead of item with ngModel (Read for more info)
  • Each input has to have a unique name

Here's the working solution for your problem.

<div>
<div *ngFor="let item of toDos;let index = index;">
  <input name=a{{index}} [(ngModel)]="toDos[index]" placeholder="item">
</div>
Below Should be binded to above input box
<div *ngFor="let item of toDos">
  <label>{{item}}</label>
</div>

Wilhelminawilhelmine answered 30/11, 2017 at 7:17 Comment(5)
I searched multiple questions until I got the answer from this one. My problem was "Each input has to have an unique name". This was exactly what I needed!Aswarm
Glad I could helpWilhelminawilhelmine
Same for me :). Saved me huge time. Thanks mateLianaliane
"Each input has to have a unique name" - This saved me. Thanks manLianaliane
"Each input has to have a unique name" fixed it for me. My input values were dissappearing after a submission but now they stay there because each input has a different name. Thanks alot!Polyethylene
A
0

Try this

@Component({
  selector: 'my-app',
  template: `
  <div>
    <div *ngFor="let item of toDos;let index = index;">
  <input [(ngModel)]="item.text" placeholder="item.text">
    </div>
    Below Should be binded to above input box
    <div *ngFor="let item of toDos">
  <label>{{item.text}}</label>
    </div>
  </div>
  `,
  directives: [MdButton, MdInput]
   })
export class AppComponent { 
  toDos: any[] =[{text:"Todo1"},{text:"Todo2"},{text:"Todo3"}];
  constructor() {}
  ngOnInit() {
  }
}
Aerify answered 30/12, 2018 at 15:27 Comment(2)
Down voting for below reasons : 1. Adding a key to the array is not required. Can be done using an array of string as well. So redundancy. 2. "trackBy:trackByIndex;" is a necessity in this case because if the an input is changed it will change the value of "toDos" array which will in-turn re-render the complete "ngFor". Using "trackBy:trackByIndex;" will not allow the re-rendering as because of this code ngFor will re-render only when the indexes will change.Adopted
Adding a key to the array shows that any object can be in the array and it can be referenced by this method, it helpful for more and deeper understanding to show that; Although that here the index is not used as showed by Lasitha Yapa belowSolubilize
M
0

You have to add a name attribute to the ngModel with name + index to make it unique.

<mat-form-field
  #fileNameRef
  appearance="fill"
  color="primary"
  floatLabel="auto"
>
  <input
    matInput
    #fileNameCtrl="ngModel"
    name="originalName{{ index }}"
    [(ngModel)]="file.originalName"
    type="text"
    autocomplete="off"
    autocapitalize="off"
    readonly
  /> 
</mat-form-field>
Minefield answered 5/4, 2021 at 11:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.