ellusion ellusion - 3 years ago 406
HTML Question

Angular2 dynamically generating Reactive forms

I have a concept question and would like some advice.
So I have a component, myFormArray, which is a reactive form. It takes in an input array, and creates a number of FormControls accordingly.

selector: 'myFormArray',
templateUrl: 'formarray.component.html'

export class FormArrayComponent{
@Input() classFields: any[];

userForm = new FormGroup();

// psuedocode here, but I know how to implement
for (# of entries in classFields)
userForm.FormArray.push(new FormControl());

Now, in my parent html, I will be dynamically generating multiple myFormArrays. If that is confusing, assume I'm doing this:

<myFormArray [classFields] = "element.subArray"/>
<myFormArray [classFields] = "element2.subArray"/>
<button (click) = "save()"> //I don't know how to implement this!

And at the very end of the page, I want a save button, that can somehow grab all the values the user inputs in to all the forms, and pushes all this data to an array in a Service component. I'm not sure exactly how to do this part. Note that I don't want individual submit buttons for each dynamically generated form component.

How would I implement this functionality? Thanks!

Answer Source

Your start is good, but you have to write your source code differently.

Instead of this example app.components.ts is main component and my-array.component.ts is child component.

Our test data

classFields1: any[] = ['firstname', 'lastname', 'email', 'password'];
classFields2: any[] = ['country', 'city', 'street', 'zipcode'];

Step 1. Use FormBuilder for form creation (app.component.ts)

You must import FormBuilder and FormGroup from @angular/forms like this:

import { FormBuilder, FormGroup } from '@angular/forms';

and then define in constructor:

constructor(private formBuilder: FormBuilder) { }

Step 2. Define new empty FormGrooup

Now you can define new empty FormGroup in ngOnInit like this:

ngOnInit() {
  this.myForm = this.formBuilder.group({});

Step 3. Create FormControls dynamically (app.component.ts)

Now you can start with dynamically creation of your FormControls by iteration of classFields. For this I would recommend to create own function. This function gets two parameter: arrayName and classFields. With arrayName we can set custom name of our FormArray-control. classFields-Array we will use for iteration. We create constant variable for empty FormArray, which we called arrayControls. After this we iterate over classFields and create for each element FormControl, which we called control, and push this control into arrayControls. At the end of this function we add our arrayControls to our Form with custom name by using arrayName. Here is an example:

createDynamicArrayControls(arrayName: string, classFields: any[]) {
    const defaultValue = null;
    const arrayControls: FormArray = this.formBuilder.array([]);
    classFields.forEach(classField => {
      const control = this.formBuilder.control(defaultValue, Validators.required);
    this.myForm.addControl(arrayName, arrayControls);

Import FormControl and FormArray from @angular/forms. Your import line should be like this:

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

Now call createDynamicFormControls-Function in ngOnInit.

Step 4. HTML Template for this dynamic Form (app.component.html)

For this example I create following template:

<h1>My Form</h1>
<form [formGroup]="myForm">
  <div formGroupName="test1">
    <app-my-array [classFields]="classFields1" [arrayFormName]="myForm.controls.test1"></app-my-array>
  <div formGroupName="test2">
    <app-my-array [classFields]="classFields2" [arrayFormName]="myForm.controls.test2"></app-my-array>
  <button type="button" (click)="saveForm()">Submit</button>

Here we have new div element with formGroupName. This group name is our arrayName in our form. We give our form arrays via @Input to my-array.component.

Step 5. MyArrayComponent

Now this component is very simnple:

import { Component, OnInit, Input } from '@angular/core';
import { FormGroup } from '@angular/forms';

  selector: 'app-my-array',
  templateUrl: './my-array.component.html',
  styleUrls: ['./my-array.component.css']
export class MyArrayComponent implements OnInit {

  @Input() classFields: any[];
  @Input() arrayFormName: FormGroup;

  constructor() { }

  ngOnInit() { }


Here we have only two @Input varibales. (I know, this variable can have a better names :-) ).

Step 6. HTML for MyArrayComponent

<div [formGroup]="arrayFormName">
  <div *ngFor="let class of arrayFormName.controls; let index = index;">
    <label [for]="classFields[index]">{{ classFields[index] }}</label>
    <input type="text" [id]="classFields[index]" [formControlName]="index" />

And here is working example on Stackblitz: https://stackblitz.com/edit/angular-wawsja

If you have some question ask me in comments or read the Angular documentation abour Reactive Forms here.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download