Skip to content Skip to sidebar Skip to footer

Nested Form Array In Angular

I am making angular application with reactive form, where i have made a nested form array which will get nested on button click. A clean working example https://stackblitz.com/edit

Solution 1:

@Undefined, you need two different jobs

  1. Create a formGroup
  2. Display inputs that manage the formGroup

the first part is the easer. Go step by step, if you select template one, you need some like

this.fb.group({
    template_name:"template one",
    template_data:this.fb.array([
          this.fb.group({
             property_one:'',
             property_two:''
          })
    ])
})

but you want to do the things dinamically, so, make a function that receive an object and return a FormGroup. As you only need the "value" of the template and the childs, your function can be like

createFormGroup(value:string,children:any[]):FormGroup
{
/*e.g. for template one, you send 
      value: "Template one",
      children: [
        { property_name: "Property one" },
        { property_name: "Property two" }
      ]
*/letcontrols:FormGroup[]=children.map(
          (x:any)=>this.fb.group({
              [x.property_name]:''
            })
     )
     returnthis.fb.group({
         template_name:value,
         template_data:this.fb.array(controls)
     })
}

So yet we can create a formGroup for the differents templates and join in a FormArray

changeEvent(e) {
    let arrayControls:FormGroup[] = [];
    //in this.selectItems we have, e.g. [1,3]for (let select of this.selectItems) {
      //search the template, select will be e.g. 1,3
      let template:any=this.templates.find(x=>x.key==select);
      switch (+select) {
        case1:
          arrayControls.push(this.createFormGroup(template.value,template.templateOneChild));
          break;
        case2:
          arrayControls.push(this.createFormGroup(template.value,template.templateTwoChild));
          break;
        case3:
          arrayControls.push(this.createFormGroup(template.value,template.templateThreeChild));
          break;
       }
    }
    this.form=this.fb.group({
       template_details:this.fb.array(arrayControls);
    })
}

See that if all ours children of templates was under a property "children" (not templateOneChild for the first, templateTwoChild for the seconds...) our function becomes in

changeEvent(e) {
    letarrayControls:FormGroup[] = [];
    //in this.selectItems we have, e.g. [1,3]for (let select ofthis.selectItems) {
      //search the template, select will be e.g. 1,3lettemplate:any=this.templates.find(x=>x.key==select);
      arrayControls.push(this.createFormGroup(template.value,template.children));
    }
    this.form=this.fb.group({
       template_details:this.array(arrayControls);
    })
}

Well you have the "form" created, now is time to show it. The form is like

<div *ngIf="form"><form [formGroup]="form"><divformArrayName="template_details"><div *ngFor="let item of details.controls;let i=index" [formGroupName]="i"><inputformControlName="template_name"><divformArrayName="template_data"><div *ngFor="let child of item.get('template_data').controls;let j=index" [formGroupName]="j"><inputformControlName="??????????"></div></div></div></div></form></div>

Yes, we have a problem, we don't know the "formControlName" of the inner formArray. One solution is have a variable "controlsName" that will be an array of array, so, if e.g. we choose 1 and 3 template our controlsName was like

controlsName=[
   ["property_one","property_two"],
   ["property_six",property_seven"]
]

Well, again make a function that return an array of strings with the names of the properties. it's a simply version of our createFormGroup, receive "children" and return an array of strings.

getControlNames(children:any[]):string[]
{
     letcontrolNames:string[]=children.map(x=>x.property_name);
     return controlNames;
}

Well, in changeEvent we call to this function after call to createFormGroup

changeEvent(e) {
    let arrayControls:FormGroup[] = [];
    let controlsName:string[] = []; //<--add this linefor (letselect of this.selectItems) {
      let template:any=this.templates.find(x=>x.key==select);
      switch (+select) {
        case1:
          arrayControls.push(this.createFormGroup(template.value,template.templateOneChild));
           controlsName.push(this.getControlNames(template.templateOneChild)); //<--and thisbreak;
        ... idem withcase2andcase3...
      }
    }
    this.controlsName=controlsName; //<--give value to name first//then create the formthis.form=this.fb.group({
       template_details:this.fb.array(arrayControls);
    })

After this, replace the < input formControlName="??????????" > by

<input [formControlName]="controlsName[i][j]"> 

See that we use [formControlName] (not formControlName) because is an evaluated expression.

See the stackblitz here

Solution 2:

I am not sure about your question.You want to dynamically add controls using json.

Reference link : https://angular.io/guide/dynamic-form

Working example : https://stackblitz.com/edit/angular-srpk3w

Replace your files with the below code :

app.component.html

<selectmultiple [(ngModel)]="selectItems" (change)="changeEvent($event)"><option *ngFor="let template of templates" [value]="template.key">{{template.value}}</option></select>

{{selectItems|json}}
<div *ngIf="form"><form [formGroup]="form"><div *ngFor="let item of array">
            {{item.value}} is the parent
      <div *ngFor="let child of item.templateChild; index as i">
        {{child.property_name}}
        <inputtype="text"formControlName="{{child.property_name.split(' ').join('_')}}"  [value]="child.property_name" ></div><br><br><br></div></form></div><br><br><br>
{{form.value|json}}

app.component.ts

import { Component } from'@angular/core';
import { FormControl, FormGroup, Validators } from'@angular/forms';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
exportclassAppComponent  {

    array: any[] = [];
    selectItems: any;
    form: FormGroup;

    templates = [
    {
      key: 1, value: "Template one",
      templateChild: [
        { property_name: "Property one" },
        { property_name: "Property two" }
      ]
    },
    {
      key: 2, value: "Template two",
      templateChild: [
        { property_name: "Property three" },
        { property_name: "Property four" },
        { property_name: "Property five" }
      ]
    },
    {
      key: 3, value: "Template three",
      templateChild: [
        { property_name: "Property six" },
        { property_name: "Property seven" }
      ]
    }
  ]


  changeEvent(e) {
    this.array = [];
    for (let select ofthis.selectItems) {
     this.array.push(this.templates[select-1])
     this.form=this.getFormValue(this.array);
      }     
  }
getFormValue(array){
  letgroup: any = {};
  array.forEach(r=>{
    r.templateChild.forEach((t,index)=>{
    group[t.property_name.replace(/ /g, "_")]= newFormControl(t.property_name);
    })
  })
returnnewFormGroup(group);;
}
}

Post a Comment for "Nested Form Array In Angular"