Search code examples
angularformarrayformgroupsangular-cdk-drag-drop

How to use cdkDropList with Angular FormArray and FormGroup?


I am trying to use a FormArray in combination with a cdkDropList. But as soon as I add the corresponding FormGroup, the cdkDrag within that group stops working. So do any other events, like click events of buttons etc, that I removed here for simplicity.

So this works:

<div cdkDropList cdkDropListOrientation="horizontal" style="display: flex;">
  <ng-container *ngFor="let group of formArray.value; let i = index" formArrayName="myFormArray">
    <div cdkDrag>
      <div cdkDragHandle style="padding: 1em; margin: 1em;background: red;">{{i}}</div>
    </div>
  </ng-container>
</div>

This doesnt:

<div cdkDropList cdkDropListOrientation="horizontal" style="display: flex;">
  <ng-container *ngFor="let group of formArray.value; let i = index" formArrayName="compositeContent">
    <div [formGroupName]="i" cdkDrag>
      <div cdkDragHandle style="padding: 1em; margin: 1em;background: red;">{{i}}</div>
    </div>
  </ng-container>
</div>

neither does this:

<div cdkDropList cdkDropListOrientation="horizontal" style="display: flex;">
  <ng-container *ngFor="let group of formArray.value; let i = index" formArrayName="compositeContent">
    <div cdkDrag>
      <div cdkDragHandle style="padding: 1em; margin: 1em;background: red;">{{i}}</div>
      <div [formGroupName]="i"></div>
    </div>
  </ng-container>
</div>

This is the formArray getter:

get formArray(): FormArray {
  return this.form.controls["formArray"] as FormArray;
}

And when adding to the FormArray from existing data from my API, this is how i create a new formArray element:

addElement(data): void {
   this.formArray.insert(this.formArray.length, this.getNewElementFormGroup(data));
}

getNewElementFormGroup(data): FormGroup {
  return new FormGroup({...});
}

Here is a stackblitz. Add [formGroupName]="i" to the cdkDrag div to reproduce the error.


Solution

  • The formArrayName must be the top element above the *ngFor.

    The main problem is that, you need to run the for loop on the controls rather the values, this solved the issue.

    Working example below!

    <form [formGroup]="form">
      <div
        cdkDropList
        cdkDropListOrientation="horizontal"
        style="display: flex;"
        (cdkDropListDropped)="drop($event)"
        formArrayName="myFormArray"
      >
        <ng-container
          *ngFor="
            let formGroup of formArray.controls;
            trackBy: trackBy;
            let i = index
          "
        >
          <div cdkDrag [formGroupName]="i">
            <div cdkDragHandle style="padding: 1em; margin: 1em;background: red;">
              {{ formGroup?.controls?.control?.value }}
            </div>
          </div>
        </ng-container>
      </div>
    </form>
    

    ts

    import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
    import { Component } from '@angular/core';
    import {
      FormBuilder,
      FormGroup,
      FormArray,
      FormControl,
      Validators,
    } from '@angular/forms';
    
    @Component({
      selector: 'my-app',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css'],
    })
    export class AppComponent {
      form: FormGroup;
    
      constructor(private formBuilder: FormBuilder) {}
    
      ngOnInit() {
        this.form = new FormGroup({
          myFormArray: new FormArray([]),
        });
        this.addElement(1);
        this.addElement(2);
        this.addElement(3);
      }
      get formArray(): FormArray {
        return this.form.controls['myFormArray'] as FormArray;
      }
      addElement(data): void {
        this.formArray.insert(
          this.formArray.length,
          this.getNewElementFormGroup(data)
        );
      }
    
      trackBy(i, item) {
        return item;
      }
    
      getNewElementFormGroup(data): FormGroup {
        return new FormGroup({
          control: new FormControl(data, []),
        });
      }
    
      drop(event: CdkDragDrop<string[]>) {
        moveItemInArray(
          this.formArray.controls,
          event.previousIndex,
          event.currentIndex
        );
      }
    }
    

    Stackblitz Demo