Search code examples
javascriptangulartypescriptangular-reactive-formsangular-forms

Angular ReactiveForms FormArray removeAt removes all elements in the FormArray


I am creating a form that consists in two FormArrays, items and savedItems. On each element in the form array there is a button that can remove that element from the array.

The top/first FormArray is populated from a list of objects. While the second FormArray is for the user to fill.

The problem is, on the first FormArray whenever I click on the "Remove Entry" button all elements in the FormArray are removed instead of the element belonging to (in the same position) the "Remove Entry" button.

Here is my form definition:

<form [formGroup]="boxForm" (ngSubmit)="onSubmit(boxForm)">
<div class="row" formArrayName="savedItems" *ngFor="let item of getSavedItems.controls; let i = index;"  >
  <div *ngFor="let trailer of trailerModelList; let x = index;" >
    <div class="form-group" [formGroupName]="i" style="margin-bottom: 10px">
      <div>
        <div class="col-sm-5 form-group">
          <label for="name">Origin</label>
          <input class="form-control" type="text" placeholder="From" value="{{trailer.fromLocation}}"/>
        </div>
        <div class="col-sm-5 form-group">
          <label for="name">Destination</label>
          <input class="form-control" type="text" placeholder="To" value="{{trailer.toLocation}}" />
        </div>
        <div class="col-sm-3 form-group">
          <label for="name">Name</label>
          <input class="form-control" type="text" placeholder="Name" value="{{trailer.name}}"/>
        </div>
        <div class="col-xs-2">
          <button class="btn btn-danger" type="button" (click)="removeSavedItem(i)">Remove Entry</button>
        </div>
      </div>
    </div>
  </div>
</div>
<hr>
<div class="row" formArrayName="items" *ngFor="let item of getItems.controls; let i = index;" >
  <div class="form-group" [formGroupName]="i">
    <div class="col-md-5 form-group" >
      <label for="name">Origin</label>
      <input class="form-control" type="text" formControlName="origin" >
    </div>
    <div class="col-md-5 form-group">
      <label for="name">Destination</label>
      <input class="form-control" type="text" formControlName="to">
    </div>
    <div class="col-sm-3 form-group">
      <label for="name">Name</label>
      <input class="form-control" type="text" formControlName="name" placeholder="Name"/>
    </div>
    <div class="col-xs-2">
      <button class="btn btn-danger" type="button" (click)="removeItem(i)">Remove Entry</button>
    </div>
  </div>
</div>
<button class="btn btn-success" type="submit"  style="margin-right: 10px">Go</button>
<button class="btn btn-primary" type="button" (click)="addItem()" style="margin-right: 10px">New Box</button>

And here is how I create, add and remove items for the FormArray:

get getItems(): FormArray {
  return this.boxForm.get('items') as FormArray;
}

get getSavedItems(): FormArray {
  return this.boxForm.get('savedItems') as FormArray;
}

createBox(): FormGroup {
  return this.formBuilder.group({
    name: ['', [Validators.required, Validators.minLength(3)]],
    origin: ['', [Validators.required, Validators.minLength(1)]],
    to: ['', [Validators.required, Validators.minLength(1)]]
  });
}

addItem(): void {
  this.getItems.push(this.createBox());
}

removeItem(index) {
  this.getItems.removeAt(index);
}

removeSavedItem(index) {
  this.getSavedItems.removeAt(index);
}

Here is a stackblitz with a reproducible code of my issue. If you click on any "Remove Entry" above the horizontal bar both entries will be removed from that element in the FormArray instead of only the selected one.

Why is this happening? I am quite sure it is related to having two for loops. One for the savedItem controls and an inner loop to loop over the elements in my list to populate the FormArray but I cannot pinpoint the issue, I am still learning my way with Angular


Solution

  • The problem is in the creating your FormArray-property savedItems, also this line:

    ...
    savedItems: this.formBuilder.array([this.createBox()])
    ...
    

    This lines means, that you define only one FormGroup in your FormArray and so you have one element with index = 0.

    In your template file you in block for savedItems you have two loops:

    1.) *ngFor="let item of getSavedItems.controls; let i = index;"

    This is right loop and will give your right functionality for removeAt.

    2.) *ngFor="let trailer of trailerModelList; let x = index;"

    This is wrong loop, because you can not create FromArray-items from template, so as you have now.

    Problem in short: By clicking on Remove entry-button both(all) savedItems will be removed from array, because you have only one element with index = 0 in FormArray.

    Solution:

    Step 1: Create for each trailerModelList-item an FormArray with createBox()-method.

      ngOnInit() {
        this.boxForm = this.formBuilder.group({
          items: this.formBuilder.array([this.createBox()]),
          savedItems: this.formBuilder.array([])
        });
    
        this.trailerModelList = new Array();
        this.trailerModelList.push(new TrailerModel(1, 'test', 'London', 'Paris'));
        this.trailerModelList.push(new TrailerModel(2, 'test2', 'Amsterdam', 'Berlin'));
    
        this.trailerModelList.forEach(item => {
          (this.boxForm.get('savedItems') as FormArray).controls.push(this.createBox(item.name, item.fromLocation, item.toLocation));
        });
      }
    
    ....
    
      createBox(name: string = null, origin: string = null, to: string = null): FormGroup {
        return this.formBuilder.group({
          name: [name, [Validators.required, Validators.minLength(3)]],
          origin: [origin, [Validators.required, Validators.minLength(1)]],
          to: [to, [Validators.required, Validators.minLength(1)]]
        });
      }
    

    Step 2: Remove following loop from template: *ngFor="let trailer of trailerModelList; let x = index;"

    Step 3: Add formControlName-property to your form items inside this loop: *ngFor="let item of getSavedItems.controls; let i = index;"

    <div class="row" formArrayName="savedItems" *ngFor="let item of getSavedItems.controls; let i = index;"  >
          <div>
            <div class="form-group" [formGroupName]="i" style="margin-bottom: 10px">
              <div>
                <div class="col-sm-5 form-group">
                  <label for="name">Origin</label>
                  <input class="form-control" type="text" placeholder="From" formControlName="origin"/>
                </div>
                <div class="col-sm-5 form-group">
                  <label for="name">Destination</label>
                  <input class="form-control" type="text" placeholder="To" formControlName="to" />
                </div>
                <div class="col-sm-3 form-group">
                  <label for="name">Name</label>
                  <input class="form-control" type="text" placeholder="Name" formControlName="name"/>
                </div>
                <div class="col-xs-2">
                  <button class="btn btn-danger" type="button" (click)="removeSavedItem(i)">Remove Entry</button>
                </div>
              </div>
            </div>
          </div>
        </div>
    

    Here is stackblitz with working solution.