Alex Beugnet
Alex Beugnet

Reputation: 4071

Issues with model bindings on Forms with several *ngFor

I'm using a form where I have the following model repeated several times :

This pattern is repeated for 2 arrays of Bottle (the Bottle type is made of an Id and a Name) as followed :

I want to be able to choose whatever type of bottle and corresponding count on the submit, but I believe I'm missing something because the behavior is all messed up :

Here is a working example in a plunkr : http://plnkr.co/edit/1z6dN6?p=preview


Here is my html file (I'm using AngularMaterial 2) :

  <div fxLayout="row" style="max-width: 80%">

    <!-- ORDERED BOTTLES -->
    <div fxLayout="column" style="min-width: 50%">
      <div fxLayout="row" style="max-width: 100%" *ngFor="let bottle of orderedBottles; let i = index">
        <md-select class="select" placeholder="Select bottle type" name="orderedTypeSelect_{{i}}" [(ngModel)]="orderedBottles[i].typeId">
          <md-option class="options" *ngFor="let type of orderedClonedArrayBottles" [value]="type.typeId">
            {{ type.name }}
          </md-option>
        </md-select>

        <md-input-container class="container">
          <input md-input type="number" name="orderedBottleInput_{{i}}" autocomplete="off" [(ngModel)]="orderedBottles[i].count"
          step="1" min="0" max="99">
        </md-input-container>

        <button class="button-row" type="button" (click)="removeRow(i, 'order')">-</button>
      </div>
    </div>

    <!-- RETURNED BOTTLES -->
    <div fxLayout="column" style="min-width: 50%">
      <div fxLayout="row" style="max-width: 100%" *ngFor="let bottle of returnedBottles; let j = index">
        <md-select class="select" placeholder="Select bottle type" name="returnedTypeSelect_{{j}}" [(ngModel)]="returnedBottles[j].typeId">
          <md-option class="options" *ngFor="let typed of returnedClonedArrayBottles" [value]="typed.typeId">
            {{ typed.name }}
          </md-option>
        </md-select>

        <md-input-container class="container">
          <input md-input type="number" name="returnedBottleInput_{{j}}" autocomplete="off" [(ngModel)]="returnedBottles[j].count"
          step="1" min="0" max="99">
        </md-input-container>

        <button class="button-row" type="button" (click)="removeRow(j, 'return')">-</button>
      </div>

    </div>
  </div>

To add some explanation, this is a child component that has a single Array of Bottle as @Input(), which I clone in 2 different arrays (orderedClonedArrayBottles and returnedClonedArrayBottles) so that my parent array isn't updated from the child updates.

I then display my bottles with the orderedBottles Array and the returnedBottles that get their values from the 2 cloned beforehand.

ngOnChanges(changes) {
    // Get @Input data when it's ready
    if (changes.bottleArray) {
      // Cloning
      //this.orderedClonedArrayBottles = [...changes.bottleArray.currentValue];
      //this.returnedClonedArrayBottles = [...changes.bottleArray.currentValue];

      this.orderedClonedArrayBottles = Array.from(changes.bottleArray.currentValue as Bottle[]);
      this.returnedClonedArrayBottles = Array.from(changes.bottleArray.currentValue as Bottle[]);

      console.log(this.orderedClonedArrayBottles);
      console.log(this.returnedClonedArrayBottles);

      // Display first rows
      if (this.orderedClonedArrayBottles.length > 0) {
        this.orderedBottles.push(this.orderedClonedArrayBottles[0]);
      }
      if (this.returnedClonedArrayBottles.length > 0) {
        this.returnedBottles.push(this.returnedClonedArrayBottles[0]);
      }
    }
  }

I have no idea why this isn't working correctly, most probably because I don't manage the *ngFor as I should. I have seen posts about trackBy for *ngFor but I have no idea if that will help or not.

Upvotes: 0

Views: 192

Answers (1)

Suraj Rao
Suraj Rao

Reputation: 29635

You are cloning the bottleArray. But in your case the bottle objects are not cloned. The same references are pushed in two arrays.

Check this answer.

You may have to use Object.assign for each bottle object. If you have nested objects you will have to traverse the properties and copy.

Upvotes: 1

Related Questions