10

I have an Angular component that has a button which adds a new object to an array that serves as a model. In my component template, I have an *ngFor loop which iterates through the objects in the array and displays input fields for the properties of the object. I also have a "remove" button next to each object that removes the object from the array. When I do the following steps the UI goes out of sync with the model and empties the fields of all items except the first one.

  1. Add 3 new objects to the array by clicking the "Add" button
  2. Populate the input fields with some data
  3. Click the Remove button on the middle item to remove it
  4. Add 1 new object by clicking the "Add" button

What is making the UI go out of sync with the model?

Here is a Plunker example that demonstrates the issue Example I also added a line in the template that shows what is in the model array.

@Component({
  selector: 'my-app',
  template: `
    <div>
    <button (click)="things.push({})">+ Add new thing</button>
      <br />
      <br />
      <form #contactForm="ngForm">
        <ng-container *ngFor="let thing of things;let i = index">
          <input [(ngModel)]="thing.name" name="name-{{i}}" id="name-{{i}}" placeholder="name"/>
          <br />
          <input [(ngModel)]="thing.otherstuff" name="other-{{i}}" id="other-{{i}}" placeholder="other" />
          <button (click)="things.splice(i, 1)">Remove</button>          
          <br />
          <br />
        </ng-container>
      </form>
      {{things | json}}
    </div>
  `,
})
export class App {
  things: Awesome[]
  constructor(){
    this.things = new Array();
  }
}

@NgModule({
  imports: [ 
    BrowserModule,
    FormsModule 
  ],
  declarations: [ App ],
  bootstrap: [ App ]
})
export class AppModule {
}

export class Awesome{
  name?: string;
  otherstuff?: string;
}
Tosho Toshev
  • 164
  • 1
  • 7
  • if there are 3 objects, when you fill their input fields with some random string, remove the middle one, and then click on the add button again it clears out everything – Tosho Toshev Sep 12 '17 at 13:23
  • This is a good question :). To fix this issue just put your form inside your *ngFor – Cristophs0n Sep 12 '17 at 14:04

1 Answers1

4

Don't use index i to give value for name attribute. i is not stable when splicing items from your array so you have to generate an unique id for each new added thing (I provided an exemple function that generate unique id).

The code below works:

//our root app component
import { Component, NgModule, VERSION } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { FormsModule } from "@angular/forms";

@Component({
    selector: 'my-app',
    template: `
    <div>
    <button (click)="addEmptyItem()">+ Add new thing</button>
      <br />
      <br />
      <form>
        <ng-container *ngFor="let thing of things">
          <input [(ngModel)]="thing.name" name="name-{{thing.id}}" id="name-{{thing.id}}" placeholder="name"/>
          <br />
          <input [(ngModel)]="thing.otherstuff" name="other-{{thing.id}}" id="other-{{thing.id}}" placeholder="other" />
          <button (click)="removeItem(thing)">Remove</button>          
          <br />
          <br />
        </ng-container>
      </form>
      {{things | json}}
    </div>
  `,
})
export class App {

    things: Awesome[]
    constructor() {
        this.things = new Array();
    }
    removeItem(thing): void {
        this.things = this.things.filter(th => th.name !== thing.name);
    }
    addEmptyItem(): void {
        let newItem = new Awesome();
        newItem.id = this.guid();
        this.things.push(newItem);
    }

    private guid() {
        let uniqueId = Math.random().toString(36).substring(2) 
           + (new Date()).getTime().toString(36);
        return uniqueId;
    }
}

@NgModule({
    imports: [
        BrowserModule,
        FormsModule
    ],
    declarations: [App],
    bootstrap: [App]
})

export class AppModule {
}

export class Awesome {
    id?: string;
    name?: string;
    otherstuff?: string;
}
Faly
  • 13,291
  • 2
  • 19
  • 37
  • This works. Thanks! At first I thought that it might be a problem with the naming of the HTML elements from this [question](https://stackoverflow.com/questions/38639560/dynamic-angular2-form-with-ngmodel-of-ngfor-elements) but now I realize that I have to provide a unique identifier for each item. Thanks, again. – Tosho Toshev Sep 12 '17 at 14:11