73

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() {
  }
}
Stefan Falk
  • 23,898
  • 50
  • 191
  • 378
Ka Tech
  • 8,937
  • 14
  • 53
  • 78

4 Answers4

120

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;
  }
}
starball
  • 20,030
  • 7
  • 43
  • 238
Ka Tech
  • 8,937
  • 14
  • 53
  • 78
  • 34
    Actually the `trackBy` does not matter in your code! What matters is `[(ngModel)]="toDos[index]"` instead of `[(ngModel)]="item"` – Lars Mar 01 '17 at 07:36
  • 23
    @Lars without the trackBy the input loses focus and seems to hang in my experience. – Reed Jan 31 '18 at 22:28
  • 17
    Without the trackBy `ngFor` re-creates the DOM every time you change a string in your array, so this is totally necessary. – Marc J. Schmidt Mar 22 '18 at 02:12
  • @Lars `trackBy:trackByIndex;` is needed otherwise you lose focus on the input after first keypress. – Ambroise Rabier Apr 28 '19 at 15:38
  • In case you have your `` inside a `
    `, 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.
    – Ambroise Rabier Apr 28 '19 at 15:48
  • Would like to add here that using [attr.name] instead of [name] will break the ngModel as well – silvio Jul 12 '19 at 17:55
  • Just wanted to comment that this solution also resolved the focus loss when using ngx-color-picker – Michael Sorensen Dec 19 '19 at 22:08
  • 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.. – ScipioAfricanus Jan 02 '22 at 05:13
68

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>

Lasitha Yapa
  • 4,309
  • 8
  • 38
  • 57
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() {
  }
}
Simone Nigro
  • 4,717
  • 2
  • 37
  • 72
Vicky
  • 127
  • 1
  • 4
  • 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. – Suyash Gulati Mar 27 '19 at 14:03
  • 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 below – Mike Jul 01 '21 at 22:08
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>
Lorka
  • 49
  • 1
  • 3