Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular2 reactive forms : syncing FormArray with parent FormGroup

I am using FormArrays to deal with object arrays of variable lengths. While my current implementation mostly works, I have a problem when it comes to validation, because any changes made to my FormArray is not propagated to its "parent" FormGroup... or at least not automatically The value of the FormGroup can be updated, by changing the value of another control managed by said FormGroup.

At this point, I can't tell if there is something wrong with my implementation, or if I'm expecting something I shouldn't... Whatever the "problem" is in the end, I'll gladly take any input at this point.

Link to the Plunker : https://plnkr.co/edit/PLslyJo1YOszGwTpujyx (Angular version is 2.0.1)

The comment at the top of app.ts in the Plunker details the steps to reproduce the problem (or what is a problem to me, anyway).

As SO asks for some code when linking to Plunker, here are the lines pertaining to the definition of the FormGroup and FormArray

this.form = this.fb.group({
        "size":[0, Validators.required],
        "someArray": this.fb.array(this.initFormArray(0), Validators.required),
        "someOtherField":[null]
});

initFormArray(size:number):FormGroup[]{
  let newFormArray:FormGroup[] = [] ;

  for(let i = 0 ; i < size ; i++){
    newFormArray.push(this.fb.group({
      fieldA: [null, Validators.required],
      fieldB: [null, Validators.required]
  })); 
}

  return newFormArray;
}

updateFormArray(value:string){
  let newSize:number = parseInt(value) ;

  if(newSize >= 0){
    this.form.controls["someArray"] = this.fb.array(this.initFormArray(newSize))
    this.form.updateValueAndValidity();
  }
}

This is stated in the Plunker, but I might as well mention here that subscribing to the valueChanges of the FormArray does not seem to help (or maybe I didn't do the right thing inside my subscription) :

this.form.controls['someArray'].valueChanges.subscribe((data) => this.form.updateValueAndValidity());

Thanks to anyone chiming in :)

EDIT (2016-10-11) : playing around with the valueChanges hook some more allowed me to virtually solve the problem, but with an unsatisfactory solution. For this reason, I'm editing rather than answering my own question, but if any mod considers this to be a valid conclusion, by all means please do what you deem adequate.

As for my "solution" : instead of subscribing to valueChanges at FormArray or root FormGroup level, I could almost achieve what I wanted by subscribing to the valueChanges observable of each FormGroup that compose the FormArray :

initFormArray(size:number):FormGroup[]{
  let newFormArray:FormGroup[] = [] ;

  for(let i = 0 ; i < size ; i++){
    let formGroup = this.fb.group({
      fieldA: [null, Validators.required],
      fieldB: [null, Validators.required]
    }) ;

    formGroup.valueChanges.subscribe((data) => this.form.updateValueAndValidity());
    newFormArray.push(formGroup); 
  }

return newFormArray;

}

By doing so, the root FormGroup is updated when the FormArray is modified, but with a lag. For instance, if I input "123" in field A, the root FormGroup value contains "fieldA":"12".

Hence my unsatisfactory "solution" : by adding a dummy setTimeout in the subscription, the root FormGroup updates exactly as I want it to... except ideally there would be no need for a setTimeout.

formGroup.valueChanges.subscribe((data) => setTimeout(()=>this.form.updateValueAndValidity(), 1);

Is there any way to manage this without resorting to setTimeout ?

The updated Plunker : https://plnkr.co/edit/7mbTNPHjRhlU3ostf3av

like image 299
phl Avatar asked Oct 06 '16 15:10

phl


People also ask

Can FormArray contains FormGroup?

The FormArray is a way to Manage collection of Form controls in Angular. The controls can be FormGroup, FormControl, or another FormArray. Because it is implemented as Array, it makes it easier dynamically add controls.

How do I use FormGroup in FormArray?

We do not have a name to the FormGroup . The Index of the element is automatically assigned as the name for the element. Hence we use the [formGroupName]="i" where i is the index of the FormArray to bind the FormGroup to the div element. Finally, we add the controls using the formControlName directive.

What is the difference between FormGroup and FormArray?

Whereas FormGroup represents an entire form or a fixed subset of a form's fields, FormArray usually represents a collection of form controls that can grow or shrink. For example, you could use FormArray to allow users to enter an arbitrary number of emails.

How do I remove FormControl from FormArray?

The key here is to navigate your form structure. Once you get to the form group that _id is in, you can use removeControl('_id') to remove the control.


2 Answers

I have the same issue of updating parent formgroup whenever nested form array changes. Try to set parent property whenever form array changes. Hope it helps.

addItem(): void {
    let fa = <FormArray>this.formGroup.get(this.formProperty)
    let fb = this.CreateNewRow(this.schema);
    fb.setParent(fa);
    fa.controls.push(fb);
  }
like image 124
Satheesh Avatar answered Oct 15 '22 09:10

Satheesh


I played somewhat with your plunker, and found out the problem

When you assigned the new FormArray after changing the size of it, you assigned it alone, not relating it correctly to the parent FormGourp.
Using this.form.controls[<controlName>] = new FormControl/ FormArray/ FormGroup; does not apply the parent property to the new control.
That's why the values did not propagate to the parent until you "refreshed" the entire form value when updating one of the parent fields.

You can see in the plunker, I added some console.logs for you to better understand.

I also added the correct way (in my opinion) to replace the value of the someArray FormArray.
Using the official documentation helped a lot.

Hope this helps

like image 31
Amir Tugi Avatar answered Oct 15 '22 10:10

Amir Tugi