ngModel two way databinding does not bind correctly inside a *ngFor loop

Issue

I have the following html inside a ngForm:

<div class="form-group form-group-flex" *ngFor="let customerRole of customerRoles; let i = index">
    <div class="role-name">
      <label for="roleName">Rolename{{i + 1}}</label>
      <input type="text" class="form-control" name='role-name' id="roleName" [(ngModel)]="customerRole.name"/>
    </div>
    <div class="hourly-wage"> // can ignore this div, works similary as role-name above
      <label for="hourlyWage">Hourly wage</label>
      <div class="input-group">
        <input type="number" class="form-control" name='hourlyWage' id="hourlyWage"
               [(ngModel)]="customerRole.hourlyWage"/>
        <div class="input-group-append">
          <span class="input-group-text ignore-radius">€</span>
        </div>
      </div>
    </div>
    <button type="button" class="btn btn-dark btn-sm" id="deleteRole" (click)="deleteRole(customerRole)" ngbTooltip="Delete role">
      <i class="fa fa-trash" aria-hidden="true"></i>
    </button>
  </div>
  <div class="row">
    <a class="add-role-icon">
      <i class="fa fa-plus" (click)="addRole()" placement="top" ngbTooltip="Add role"></i>
    </a>
    <label *ngIf="customerRoles.length === 0; else addMoreRoles">Add role</label>
    <ng-template #addMoreRoles>
      <label>Add another role</label>
    </ng-template>
  </div>
</div>

The addRole() method looks like this:

    addRole() {
    this.customerRoles.push(createInitialRole());
  }

customerRoles is just an array containing roles.

And thats how it looks:

enter image description here

So theres always a new role, containing a rolename and a hourlyWage, getting added in everytime i press the “add another role” button.
I have problems with the ngModel binding, everytime i add another role, every inputs value changes to the value of the last element added to the customerRoles array for some reason.

So if i added another role to the image above, it would look like this:

enter image description here

Every inputs value in the view changes to the value of the last role added, which in this case contains an empty name and an empty hourly wage. Role 1 and 2 bind to the value of Role 3 after its added in for some reason. The customerRoles array contains the correct elements. It for example still contains the junior softwaredeveloper role at the 0th index. Why does it suddenly display the latest added value to the array instead of the value its binded to? If i use

value="{{customerRole.name}}"
(input)="customerRole.name= $event.target.value"

instead of

[(ngModel)]="customerRole.name"

everything works as expected, but thats a workaround i dont want to take.

Edit:

Just for testing purposes, if i display customerRole.name in the label above the input field like this

<label for="roleName">{{customerRole.name}}</label>
<input type="text" class="form-control" name='role-name' id="roleName" [(ngModel)]="customerRole.name"/>

the label keeps showing the correct value while the input field, switches to the value of the last element added like previously mentioned.

Solution

Your HTML generated by the ngFor will be incorrect as all the roleName inputs have the same name and id. I think this might cause the ngFor to misbehave.

due to this part ... name='role-name' id="roleName" ... (and the same for the other ones)

Remove the names and ids (or make them unique per iteration) and all should work again

Answered By – The Fabio

Answer Checked By – Clifford M. (AngularFixing Volunteer)

Leave a Reply

Your email address will not be published.