5

Hya,

I have the below setup:

App.Component.Ts contents

carForm: FormGroup;

constructor(
    private fb: FormBuilder
  ) { 
    this.carForm= this.fb.group({
      name: '',
      type: '',
      extras: this.fb.array([])
    });
  }

get carExtras(): FormArray {
    return this.carForm.get('extras') as FormArray;
  }

addNewExtra() {
   this.carExtras.push(this.fb.group(new Extra());
}

Extra Model

export class Extra {
name: string = '';
description: string = '';
}

Now lets say i add 4 new Extras, the array would look as follows:

1. name = "Phantom Wheels", description = "Big dark wheels coz driver is overcompensating"
2. name = "Clearshield", description = "Simple tint that we overcharge customers"
3. name = "Rainbow Paint Job", description = "Leftover random paints mixed and thrown onto car"
4. name = "Slick Rims", description = "Major overcompensation"

I want to be able to programmatically change the order of the 4 items listed. Say i click up button next to "Slick Rims", it will swap positions with "Rainbow Paint Job" item. If i press it again it will swap positions with "Clearshield" with result as follows.

1. name = "Phantom Wheels", description = "Big dark wheels coz driver is overcompensating"
2. name = "Slick Rims", description = "Major overcompensation"
3. name = "Clearshield", description = "Simple tint that we overcharge customers"
4. name = "Rainbow Paint Job", description = "Leftover random paints mixed and thrown onto car"

Same principle if i press the down button for the entry.

Any ideas how to achieve this, its doing my head in on how to achieve this with a FormArray.

1

2 Answers 2

14

Assuming your form array looks like this in HTML:

  <div formArrayName="extras">
    extras:
    <div *ngFor="let extra of carForm.get('extras').controls; let i=index" [formGroupName]="i" >
      <input formControlName="name">
      <input formControlName="description">
      <button (click)="moveUp(i)"> UP </button>
      <button (click)="moveDown(i)"> DOWN </button>
    </div>
  </div>

you can create moveUp and moveDown functions to swap values at indexes (i-1, i) or (i, i+1) if it is possible

  moveUp(index: number) {
    if (index > 0) {
          const extrasFormArray = this.carForm.get('extras') as FormArray;
          const extras = extrasFormArray.value;
          const newExtras = this.swap(extras, index - 1, index); 
          extrasFormArray.setValue(newExtras);
    }
  }

  moveDown(index: number) {
    const extrasFormArray = this.carForm.get('extras') as FormArray;
    const extras = extrasFormArray.value;
    if (index < extras.length - 1) {
      const newExtras = this.swap(extras, index, index + 1); 
      extrasFormArray.setValue(newExtras);
    }
  }

swap function:

  swap(arr: any[], index1: number, index2: number): any[] {
    arr = [...arr];
    const temp = arr[index1];
    arr[index1] = arr[index2];
    arr[index2] = temp;
    return arr;
  }

demo: https://stackblitz.com/edit/angular-bhcdcf

EDIT: Actually we can simplify that to single function

  swap(index1: number, index2: number) {
    const extrasFormArray = this.carForm.get('extras') as FormArray;
    const extras = [...extrasFormArray.value];
    if (index2 > 0 && index1 < extras.length - 1) {
      [extras[index1], extras[index2]] = [extras[index2], extras[index1]];
      extrasFormArray.setValue(extras);
    }
  }

which would be called with appropriate indexes

  <button (click)="swap(i - 1, i)"> UP </button>
  <button (click)="swap(i, i + 1)"> DOWN </button>

demo: https://stackblitz.com/edit/angular-9gifvd

EDIT 2 by Aeseir:

Further simplification and following DRY principle (at least attempting to):

get extras(): FormArray {
  return this.carForm.get('extras') as FormArray;
}


moveUp(index: number) {
  var extraTmp = this.extras.at(index);
  this.removeExtra(index);
  this.extras.insert(index-1, extraTmp);
}

moveDown(index: number) {
  var extraTmp = this.extras.at(index-1);
  this.removeExtra(index-1);
  this.extras.insert(index, extraTmp);
}

removeExtra(index: number) {
  this.extras.removeAt(index);
}
Sign up to request clarification or add additional context in comments.

3 Comments

thanks for that, i managed to develop almost exactly same solution last night after spending a whole day looking at the construct of FormArray.
I've added another edit to further simplify the solution based on what i figured out yesterday.
I changes moveDown(index: number) { var extraTmp = this.extras.at(index+1); this.removeExtra(index+1); this.extras.insert(index, extraTmp); }
0

If you have FormGroup in FormArray and FormGroup also has FormArray, you'll want to just swap them around. The next example works for me:

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

const swap = <T>(items: T[], a: number, b: number): T[] => {
  switch (true) {
    case a < 0:
    case b < 0:
    case a > items.length - 1:
    case b > items.length - 1:
      throw new Error('Undefined swap index');
  }
  return items.map((_, i) => {
    switch (i) {
      case a:
        return items[b];
      case b:
        return items[a];
      default:
        return items[i];
    }
  });
};

export const swapFormArray = <T extends FormArray>(formArray: T, a: number, b: number): void => {
  formArray.controls = swap(formArray.controls, a, b);
  formArray.updateValueAndValidity();
};

usage:

swapFormArray(formArr, i, i - 1);
swapFormArray(formArr, i, i + 1);

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.