Dynamic nested form controls Angular 2

898 Views Asked by At

my API response is like

"groups": [{
          "group_name": "GRP1"
          "attributes": [{
                        "attribute_id": 1,
                        "attribute_name": "Frequency",
                        "value_reference": "tag"
                        }]
          },
          {
            "group_name": "GRP2"
           "attributes": [{
                        "attribute_id": 2,
                       "attribute_name": "Date",
                        "value_reference": "static",
                        "value_static_type": "date"
                      }]
              }]  

-- How I can create formConrol in Angular 4 to display data like

GroupName  
   List of Group's Attribute  
GroupName
    List of Group's Attribute  

My initial form control is like

this.editTemplateForm = this.fb.group({
            groups: this.fb.group({
                group_name: ['', Validators.required ],
                attributes : this.fb.group({
                    value_reference : []
                })
            }),
        });  

I don't understand how add control dynamically

1

There are 1 best solutions below

8
On

The structure here ends up being fairly complicated, if you'd like to match your API response exactly. Since, so far, each attributes property only has a single object of attributes, you could potentially make attributes an Object directly, instead of an array of objects, which would simplify the following code. The code below matches your current API structure, and can be played with at this working plunker.

Some things to keep in mind:

  • FormGroup needs to be the outer-most form object, and can also be used to hold an indeterminate number of FormControl items
  • FormArray, on the other hand, is useful when the number of controls is indeterminate, and it does not matter what those controls are called.
  • this is why, below, allGroups is an array holding the largest groups, and attributes is an array holding your attribute objects, but those attribute objects themselves are groups - because we want to be able to name the controls based on their API property name

There's also a good chance this structure isn't exactly what you're looking for (e.g. perhaps you didn't want all these values to be editable <input> fields), but it should give you a strong basis to play with on the plunker, and understand how you can dynamically generate HTML based on changing form objects.

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

interface APIGroup {
    'group_name': string;
    'attributes': Array<GroupAttributes>
}
interface GroupAttributes {
    'attribute_id': number;
    'attribute_name': string;
    'value_reference': string;
    'value_static_type'?: string;
}

@Component({
  selector: 'app-child',
  template: `
    <div>
      <h3>I'm the Child component</h3>
    </div>
    <form [formGroup]="editTemplateForm">
        <div formArrayName="allGroups">
            <div *ngFor="let group of editTemplateForm.get('allGroups').controls; let i=index" [formGroupName]="i">
                <input formControlName="groupName" />
                <div formArrayName="attributes">
                    <div *ngFor="let attributeGroup of group.get('attributes').controls; let n=index" [formGroupName]="n">
                        <input *ngFor="let key of keysOfFormGroup(attributeGroup)" [formControlName]="key" />
                    </div>
                </div>
                <br/>
            </div>
        </div>
    </form>
    <pre style="background: #ddd">{{editTemplateForm.value | json}}</pre>
  `,
})
export class ChildComponent {
    constructor(
        private fb: FormBuilder
    ) { }

    sampleData: Array<APIGroup> = [
        {
            "group_name": "GRP1",
            "attributes": [{
                "attribute_id": 1,
                "attribute_name": "Frequency",
                "value_reference": "tag"
            }]
        },
        {
            "group_name": "GRP2",
            "attributes": [{
                "attribute_id": 2,
                "attribute_name": "Date",
                "value_reference": "static",
                "value_static_type": "date"
            }]
        }
    ]

    editTemplateForm: FormGroup;

    ngOnInit() {
        this.editTemplateForm = this.fb.group({
            allGroups: this.fb.array([])
        });

        // would call on a subscription to actual api data
        this.sampleData.forEach(group => {
            (<FormArray>this.editTemplateForm.get('allGroups'))
                .push(this.initGroup(group));            
        });
    }

    initGroup(apiGroup: APIGroup): FormGroup {
        let formGroup = this.fb.group({
            groupName: [apiGroup.group_name],
            attributes: this.fb.array([])
        });
        apiGroup.attributes.forEach(attributeGroup => {
            (<FormArray>formGroup.get('attributes'))
                .push(this.initAttributeGroup(attributeGroup));
        });
        return formGroup;
    }

    initAttributeGroup(attributes: GroupAttributes): FormGroup {
        let formGroup = this.fb.group({});
        Object.keys(attributes).forEach(name => {
            formGroup.addControl(name, new FormControl(attributes[name]));
        });
        return formGroup;
    }

    keysOfFormGroup(group: FormGroup): Array<string> {
        return Object.keys(group.controls);
    }
}