Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deep copy of Angular Reactive Form?

I'm trying to build a function that will produce a copy of a given FormGroup. I started with:

function copyForm(form: FormGroup): FormGroup {
  const copy = new FormGroup({});
  for (let key of Object.keys(form.value)) {
    const control = form.controls[key];

    /* Copy the data from the control into a new control */
    const copyControl = new FormControl({[key]: control.value});

    copy.addControl(key, copyControl);
 }

But that doesn't work if there is a FormArray or FormGroup. This one might work if it were recursive, but I couldn't get a good handle on it.

I also attempted to solve it with

function copyForm(form: FormGroup): FormGroup {
  const copy = new FormGroup({});
  for (let key of Object.keys(form.value)) {
    const control = form.controls[key];
    const copyControl = new FormControl({...control.value});
    copy.addControl(key, copyControl);
  }
  return copy;

}

But that didn't work for double-nested FormGroups, any FormArrays or regular FormControls...

I also tried:

function copyForm(form: FormGroup): FormGroup {
  const copy = new FormGroup(Object.assign({}, form.value));
  return copy;
}

But that gives me the error:

ERROR TypeError: control.setParent is not a function

I'm stumped.

like image 632
vince Avatar asked Jan 17 '18 19:01

vince


2 Answers

This is the deep copy function I came up with which also retains the associated validator / async validator functions and disabled status of each AbstractControl.

/**
 * Deep clones the given AbstractControl, preserving values, validators, async validators, and disabled status.
 * @param control AbstractControl
 * @returns AbstractControl
 */
export function cloneAbstractControl<T extends AbstractControl>(control: T): T {
  let newControl: T;

  if (control instanceof FormGroup) {
    const formGroup = new FormGroup({}, control.validator, control.asyncValidator);
    const controls = control.controls;

    Object.keys(controls).forEach(key => {
      formGroup.addControl(key, cloneAbstractControl(controls[key]));
    })

    newControl = formGroup as any;
  }
  else if (control instanceof FormArray) {
    const formArray = new FormArray([], control.validator, control.asyncValidator);

    control.controls.forEach(formControl => formArray.push(cloneAbstractControl(formControl)))

    newControl = formArray as any;
  }
  else if (control instanceof FormControl) {
    newControl = new FormControl(control.value, control.validator, control.asyncValidator) as any;
  }
  else {
    throw new Error('Error: unexpected control value');
  }

  if (control.disabled) newControl.disable({emitEvent: false});

  return newControl;
}
like image 152
John Avatar answered Sep 20 '22 17:09

John


If you have simple FormGroups that contain only FormControls (i.e. not FormGroups or FormArrays), and you know their structure in advance, then this is a simple solution:

Note: by using initaliseFormGroup for initalising the original FormGroup, you will match the validators for each control.

initaliseFormGroup(){
    return new FormGroup({
        x : new FormControl('', Validators.required),
        y : new FormControl('', Validators.minLength(10))
    });
}

cloneFormGroup(oldForm: FormGroup){
    let newForm = this.initaliseFormGroup()
    newForm.patchValue(oldForm.value);
    return newForm;
}

If you have more complex forms (with child FormGroups), or want to clone them dynamically without knowing their structure in advance, then the other answers will better suited.

like image 33
Richard Dunn Avatar answered Sep 16 '22 17:09

Richard Dunn