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" }
      ]
*/

     let controls:FormGroup[]=children.map(
          (x:any)=>this.fb.group({
              [x.property_name]:''
            })
     )
     return this.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) {
        case 1:
          arrayControls.push(this.createFormGroup(template.value,template.templateOneChild));
          break;
        case 2:
          arrayControls.push(this.createFormGroup(template.value,template.templateTwoChild));
          break;
        case 3:
          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) {
    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);
      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">
    <div formArrayName="template_details">
      <div *ngFor="let item of details.controls;let i=index" [formGroupName]="i">
        <input formControlName="template_name">

        <div formArrayName="template_data">
          <div *ngFor="let child of item.get('template_data').controls;let j=index" [formGroupName]="j">
         <input formControlName="??????????">
          </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[]
{
     let controlNames: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 line
    for (let select of this.selectItems) {
      let template:any=this.templates.find(x=>x.key==select);
      switch (+select) {
        case 1:
          arrayControls.push(this.createFormGroup(template.value,template.templateOneChild));
           controlsName.push(this.getControlNames(template.templateOneChild)); //<--and this
          break;
        ... idem with case 2 and case 3...
      }
    }
    this.controlsName=controlsName; //<--give value to name first
    //then create the form
    this.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

<select multiple [(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}}
        <input type="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' ]
})
export class AppComponent  {

    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 of this.selectItems) {
     this.array.push(this.templates[select-1])
     this.form=this.getFormValue(this.array);
      }     
  }
getFormValue(array){
  let group: any = {};
  array.forEach(r=>{
    r.templateChild.forEach((t,index)=>{
    group[t.property_name.replace(/ /g, "_")]= new FormControl(t.property_name);
    })
  })
return new FormGroup(group);;
}
}

Post a Comment for "Nested Form Array In Angular"