Nested form array in angular

元气小坏坏 提交于 2020-01-17 01:19:54

问题


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/angular-thhczx , it has static inputs and hence on click over Add new template, it will add a another nested part and for Add new property, it will generate another property array..

So you had got the above working example concept right??

I would like to have the same json but not with add button and with dropdown.

Html of dropdown:

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

{{selectItems|json}}

<form [formGroup]="form">
    <div *ngFor="let item of array">
            {{item.value}} is the parent
      <div *ngFor="let child of item.templateChild">
        {{child.property_name}}
        <input type="text" [value]="child.property_name">
        </div>
        <br><br><br>
      </div>
      </form>
 <br><br><br>
{{form.value|json}}

Templates array: which gives value for dropdown

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

Also made a stackblitz link for the above https://stackblitz.com/edit/angular-1sg5cv

Here if i select the option template one and template two (as the selectbox is multi select) from the dropdown then i am expecting the output as,

"template_details" : [
    { "template_name": "Template One",
      "template_data" : [{"property_one": "", "property_two":""}]
    },
    { "template_name": "Template Two",
      "template_data" : [{"property_three": "", "property_four":"", 
                            "property_five":""}]
    }
    ]

If you click over the two options of template one and two you can see that you will get two and three input boxes respectively...

I wish to generate the input boxes with property names automatically under each template on selection of dropdown values..

So in simple need to convert dropdown demo as like the static inputs with add button with the same nested json structure.

I kindly request angular experts to help me in generation of input boxes based on property names for the selected template's..

I did my level best in it unable to get the solution please help me to form nested array json on based on selection of dropdown..


回答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




回答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);;
}
}


来源:https://stackoverflow.com/questions/53538849/nested-form-array-in-angular

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!