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));
}
0

4 Answers 4

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);
}

Sign up to request clarification or add additional context in comments.

1 Comment

this.arrayControls has to be updated to your array, but works great thanks!
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));
  }

2 Comments

for reference this link is useful. Exploring Drag and Drop with the new Angular Material CDK by tim_deschryver in AngularInDepth link.medium.com/ONGISEADi5
In my case i had removed "cdkDrag" from <div [formGroupName]="i" class="basic-container" cdkDrag> to resolve index issue
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));
}

Comments

0
const form_array = ...
const form_group = form_array.at(event.previousIndex);
form_array.removeAt(event.previousIndex);
form_array.insert(event.currentIndex, form_group);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.