Draggable formGroups in formArray (reactive forms)
Asked Answered
M

4

11

In angular drag-drop module, they provided documentation for the moveItemInArray() function, by using this we can only drag content in an array only. but, how we could shuffle (formGroups/formControls) in formArray?

Even I tried this moveItemInFormArray() function, as mentioned here https://github.com/angular/angular/issues/27171. but I can not make it work.

groupDrag.component.html

    <form [formGroup]="exampleForm">
      <div formArrayName="formUnits" cdkDropList (cdkDropListDropped)="drop($event)" *ngFor="let unit of exampleForm.controls.formUnits.controls; let i=index" class="rowGroup">
        <div [formGroupName]="i" class="basic-container" cdkDrag>
          <div class="row row-container" >
            <button type="button" class="drag-handle" mat-icon-button cdkDragHandle>
              <mat-icon>unfold_more</mat-icon>
            </button>

            <!-- label input field -->
            <mat-form-field  class="col-lg-4"> 
              <input matInput placeholder="Please enter label without spaces" formControlName="label" required>  
            </mat-form-field>

            <!-- options input field -->
              <mat-form-field  class="col-lg-3"> 
                <input matInput placeholder="Enter Placeholdertext" formControlName="placeholder">
             </mat-form-field>

          </div>
        </div>
      </div>
    </form>

groupDrag.component.ts

drop(event: CdkDragDrop<FormGroup[]>) {
  console.log('drop event triggers')
  this.formArray = this.exampleForm.get('formUnits') as FormArray;
  const from = event.previousIndex;
  const to = event.currentIndex;
  this.moveItemInFormArray(this.formArray, from, to)
}

/**
* Moves an item in a FormArray to another position.
* @param formArray FormArray instance in which to move the item.
* @param fromIndex Starting index of the item.
* @param toIndex Index to which he item should be moved.
*/
moveItemInFormArray(formArray: FormArray, fromIndex: number, toIndex: number): void {
  const from = this.clamp(fromIndex, formArray.length - 1);
  const to = this.clamp(toIndex, formArray.length - 1);

  if (from === to) {
    return;
  }

  const delta = from > to ? 1 : -1;
  for (let i = from; i * delta < to * delta; i += delta) {
    const previous = formArray.at(i);
    const current = formArray.at(i + delta);
    formArray.setControl(i, current);
    formArray.setControl(i + delta, previous);
  }
}

/** Clamps a number between zero and a maximum. */
clamp(value: number, max: number): number {
  return Math.max(0, Math.min(max, value));
}
Mainly answered 15/5, 2019 at 12:33 Comment(0)
M
14

This is my working solution

in component.html

    <form [formGroup]="exampleForm">
      <div cdkDropList (cdkDropListDropped)="drop($event)">
        <div formArrayName="formUnits" class="rowGroup"
          *ngFor="let unit of exampleForm.controls.formUnits.controls; let i=index"  
           cdkDrag>
          <div [formGroupName]="i" class="basic-container" cdkDrag>
            <div class="row row-container" >
              <button type="button" class="drag-handle" mat-icon-button cdkDragHandle>
                <mat-icon>unfold_more</mat-icon>
              </button>

              <!-- label input field -->
              <mat-form-field  class="col-lg-4"> 
                <input matInput placeholder="Please enter label without spaces" 
                formControlName="label" required>  
              </mat-form-field>

              <!-- options input field -->
              <mat-form-field  class="col-lg-3"> 
                <input matInput placeholder="Enter Placeholdertext" formControlName="placeholder">
             </mat-form-field>

          </div>
        </div>
      </div>
    </div>
   </form>

in component.ts file

  drop(event: CdkDragDrop<string[]>) {
    this.formArray = this.exampleForm.get('formUnits') as FormArray;
    const from = event.previousIndex;
    const to = event.currentIndex;
    this.moveItemInFormArray(this.formArray, from, to);
  }

  /**
 * Moves an item in a FormArray to another position.
 * @param formArray FormArray instance in which to move the item.
 * @param fromIndex Starting index of the item.
 * @param toIndex Index to which he item should be moved.
 */
  moveItemInFormArray(formArray: FormArray, fromIndex: number, toIndex: number): void {
    const from = this.clamp(fromIndex, formArray.length - 1);
    const to = this.clamp(toIndex, formArray.length - 1);

    if (from === to) {
      return;
    }

    const previous = formArray.at(from);
    const current = formArray.at(to);
    formArray.setControl(to, previous);
    formArray.setControl(from, current);
  }

  /** Clamps a number between zero and a maximum. */
  clamp(value: number, max: number): number {
    return Math.max(0, Math.min(max, value));
  }
Mainly answered 7/4, 2020 at 2:7 Comment(2)
for reference this link is useful. Exploring Drag and Drop with the new Angular Material CDK by tim_deschryver in AngularInDepth link.medium.com/ONGISEADi5Mainly
In my case i had removed "cdkDrag" from <div [formGroupName]="i" class="basic-container" cdkDrag> to resolve index issuePeggy
L
20

Here is working example:

groupDrag.component.ts

import {moveItemInFormArray} from "./move-item-in-form-array";

drop(event: CdkDragDrop<string[]>) {
    moveItemInFormArray(this.arrayControls, event.previousIndex, event.currentIndex);
}

move-item-in-form-array.ts

import {FormArray} from '@angular/forms';

/**
 * Moves an item in a FormArray to another position.
 * @param formArray FormArray instance in which to move the item.
 * @param fromIndex Starting index of the item.
 * @param toIndex Index to which he item should be moved.
 */
export function moveItemInFormArray(formArray: FormArray, fromIndex: number, toIndex: number): void {
  const dir = toIndex > fromIndex ? 1 : -1;

  const from = fromIndex;
  const to = toIndex;

  const temp = formArray.at(from);
  for (let i = from; i * dir < to * dir; i = i + dir) {
    const current = formArray.at(i + dir);
    formArray.setControl(i, current);
  }
  formArray.setControl(to, temp);
}

Loblolly answered 24/3, 2020 at 12:53 Comment(1)
this.arrayControls has to be updated to your array, but works great thanks!Quire
M
14

This is my working solution

in component.html

    <form [formGroup]="exampleForm">
      <div cdkDropList (cdkDropListDropped)="drop($event)">
        <div formArrayName="formUnits" class="rowGroup"
          *ngFor="let unit of exampleForm.controls.formUnits.controls; let i=index"  
           cdkDrag>
          <div [formGroupName]="i" class="basic-container" cdkDrag>
            <div class="row row-container" >
              <button type="button" class="drag-handle" mat-icon-button cdkDragHandle>
                <mat-icon>unfold_more</mat-icon>
              </button>

              <!-- label input field -->
              <mat-form-field  class="col-lg-4"> 
                <input matInput placeholder="Please enter label without spaces" 
                formControlName="label" required>  
              </mat-form-field>

              <!-- options input field -->
              <mat-form-field  class="col-lg-3"> 
                <input matInput placeholder="Enter Placeholdertext" formControlName="placeholder">
             </mat-form-field>

          </div>
        </div>
      </div>
    </div>
   </form>

in component.ts file

  drop(event: CdkDragDrop<string[]>) {
    this.formArray = this.exampleForm.get('formUnits') as FormArray;
    const from = event.previousIndex;
    const to = event.currentIndex;
    this.moveItemInFormArray(this.formArray, from, to);
  }

  /**
 * Moves an item in a FormArray to another position.
 * @param formArray FormArray instance in which to move the item.
 * @param fromIndex Starting index of the item.
 * @param toIndex Index to which he item should be moved.
 */
  moveItemInFormArray(formArray: FormArray, fromIndex: number, toIndex: number): void {
    const from = this.clamp(fromIndex, formArray.length - 1);
    const to = this.clamp(toIndex, formArray.length - 1);

    if (from === to) {
      return;
    }

    const previous = formArray.at(from);
    const current = formArray.at(to);
    formArray.setControl(to, previous);
    formArray.setControl(from, current);
  }

  /** Clamps a number between zero and a maximum. */
  clamp(value: number, max: number): number {
    return Math.max(0, Math.min(max, value));
  }
Mainly answered 7/4, 2020 at 2:7 Comment(2)
for reference this link is useful. Exploring Drag and Drop with the new Angular Material CDK by tim_deschryver in AngularInDepth link.medium.com/ONGISEADi5Mainly
In my case i had removed "cdkDrag" from <div [formGroupName]="i" class="basic-container" cdkDrag> to resolve index issuePeggy
H
0

The previous solution by @Gopal isn't valid, as after moving an item, the remaining items must shift up or down, depending on dragging direction. Here is a solution that works ok:

form = this.formBuilder.group({
  field1: [null, Validators.required],
  field2: [null, Validators.required],
  attributes: this.formBuilder.array([], Validators.required)
});

get attributes(): FormArray {
  return this.form.get('attributes') as FormArray;
}

dragDrop(event: CdkDragDrop<AbstractControl[]>) {
  this.moveItemInFormArray(this.attributes, event);
}

moveItemInFormArray(formArray: FormArray, event: CdkDragDrop<AbstractControl[]>): void {
    const from = this.clamp(event.previousIndex, formArray.length - 1);
    const to = this.clamp(event.currentIndex, formArray.length - 1);

    if (from === to) {
      return;
    }

    const target = formArray.at(from);
    const delta = to < from ? -1 : 1;

    for (let i = from; i !== to; i += delta) {
      const current = formArray.at(i + delta);
      formArray.setControl(i, current);
    }

    formArray.setControl(to, target);
  }

clamp(value: number, max: number): number {
  return Math.max(0, Math.min(max, value));
}
Hector answered 9/3, 2023 at 11:12 Comment(0)
G
0
const form_array = ...
const form_group = form_array.at(event.previousIndex);
form_array.removeAt(event.previousIndex);
form_array.insert(event.currentIndex, form_group);
Guevara answered 24/4, 2023 at 23:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.