July 10, 2020

How to create angular reactive forms dynamically?

If you are an angular developer, at least once you should come across angular forms and you should be curious about how to develop forms in a good way.

For eg. If you have some limited fields form as user details form at that time what you will do,

  • created static input fields, labels and buttons
  • registered them with reactive forms
  • added some validation to them

that’s it for small form with 5 to 7 fields it’s manageable. If someone wants to change label or button then he will go to HTML and change it directly

But consider if you have huge application 100’s of fields then its unmanageable because HTML will be huge and changing into that will result in some error most of the time.

So here dynamic forms come into the picture where we will render form fields dynamically on the basis of a javascript object or JSON and if you want to change anything you just need to update in your javascript object. No need to touch your HTML or reactive forms configuration.

Let’s do it how to implement angular dynamic reactive forms

Before starting please install angular CLI to create a project

npm install @angular/cli -g

after installing angular CLI create an angular project by using the following commands

ng new angular-dynamic-forms  --routing

Now angular project is created with initial setup.

Before starting out we need to decide what will be the forms fields which we are going to use. Below are the some fields which basically required to construct form field

for eg.

  1. id
  2. value
  3. controlType // decides type of field for eg. textbox, dropdown etc
  4. placeholder
  5. option // mostly used for dropdown fields
  6. label
  7. required
  8. errorMessage
  9. validationMessage
  10. pattern // might be any regx pattern you want use

above are the some fields which will be used to create one particular field

Please subscribe for web development tutorials

Let’s create model class for it which named as field.model.ts

export class FieldModel {
    id: string;
    value: any;
    controlType: string;
    placeHolder: string;
    options: DropdownOption[];
    label: string;
    required: boolean;
    errorMessage: string;
    validationMessage: string;
    pattern: any;
    
    constructor(field: {
        id?: string;
    value?: any;
    controlType?: string;
    placeHolder?: string;
    options?: DropdownOption[];
    label?: string;
    required?: boolean;
    errorMessage?: string;
    validationMessage?: string;
    pattern?: any;
    }){
        if(field){
            this.id = field.id;
            this.value = field.value;
            this.controlType = field.controlType;
            this.placeHolder = field.placeHolder;
            this.options = field.options;
            this.label = field.label;
            this.required = field. required;
            this.errorMessage = field.errorMessage;
            this.validationMessage = field.validationMessage;
            this.pattern = field.pattern;
        }

    }
}

class DropdownOption {
    code: string;
    value: any
}

above is our HTML form field javascript representation where you find above field mostly

NOTE: all the property in the fieldModel class not applicable for every form field. For eg. options will be applicable in case of the dropdown and radio only

Now let’s create a JSON object of fields array which we will be going to render as HTML form field.

[
    {
        "id": "nameField",
        "value": "",
        "controlType": "textbox",
        "placeHolder": "Enter Name",
        "label": "Name",
        "required": true,
        "errorMessage": "Please enter name"
        
    },
    {
        "id": "genderField",
        "value": "",
        "controlType": "radioGroup",
        "placeHolder": "Please select gender",
        "label": "Gender",
        "required": true,
        "options": [
            {
                "code": "Male",
                "value": "male"
            },
            {
                "code": "Female",
                "value": "female"
            }
        ],
        "errorMessage": "Please select gender"
        
    },
    {
        "id": "genderField",
        "value": "",
        "controlType": "dropdown",
        "placeHolder": "Please select profession",
        "label": "Profession",
        "required": true,
        "options": [
            {
                "code": "Prof1",
                "value": "prof1"
            },
            {
                "code": "Prof2",
                "value": "prof2"
            }
        ],
        "errorMessage": "Please select gender"
        
    }
]

above is the json object of field array which we going to render on HTML.

Let’s create field component which will render for each field in the json array

//field.component.html

<div [formGroup]="form" class="field-section">
    <label [attr.for]="field.id">{{field.label}}</label>

    <div [ngSwitch]="field.controlType">
        <section>
            <input *ngSwitchCase="'textbox'" [formControlName]="field.id" [id]="field.id" type="text">
        </section>
        <section *ngSwitchCase="'dropdown'">
            <select [id]="field.id" [formControlName]="field.id">
                <option *ngFor="let opt of field.options" [value]="opt.code">{{opt.value}}</option>
            </select>
        </section>

        <section *ngSwitchCase="'radioGroup'">
            <span *ngFor="let opt of field.options">
            <input  type="radio" [value]='opt.code' [formControlName]="field.id">{{opt.value}}
            </span>
        </section> 
    </div>

    <div class="errorMessage" *ngIf="!isValid">{{field.errorMessage}} is required</div>
</div>
//field.component.ts
import { Component, OnInit, Input } from '@angular/core';
import { FieldModel } from 'src/app/models/field.model';
import { FormGroup } from '@angular/forms';

@Component({
  selector: 'app-field',
  templateUrl: './field.component.html',
  styleUrls: ['./field.component.css']
})
export class FieldComponent implements OnInit {
  @Input() field: FieldModel;
  @Input() form: FormGroup;
  get isValid() { 
    return !this.form.controls[this.field.id].touched || this.form.controls[this.field.id].valid; 
  }
  constructor() { }

  ngOnInit() {
  }

}

In the above code, as we can see we have created field component in which we are passing form and field as input where it will decide particular field belongs to which form and which field to render.

In the above field component, we are just rendering three types of field, for now, to render like text, dropdown and radio group fields

You still can add other form fields control type like checkobx, textarea, button, etc.

Now let’s create a form control service which will take care of reactive forms configuration

considering you have knowledge of how reactive forms work, If not just go through this link.

import { Injectable } from '@angular/core';
import { FieldModel } from '../models/field.model';
import { FormControl, Validators, FormGroup } from '@angular/forms';

@Injectable({
  providedIn: 'root'
})
export class FormControlService {

  constructor() { }

  getFormGroupObject(fields: FieldModel[]){
    let formGroup = {};
    fields.forEach(field=>{
      formGroup[field.id] = field.required 
        ? new FormControl(field.value,Validators.required)
        : new FormControl(field.value)
    });
    return new FormGroup(formGroup);
  }

}

As we know, we need to create formGroup object for each field to add that property in the form context. We have given field.id as model for each individual field which is the same as what we have given in formControlName.

Now, Let’s create container component where we will create our form

//personalInfo.component.html

<section>
    <div *ngFor="let field of fields">
        <app-field [form]="personalInfoForm" [field]="field"></app-field>

    </div>

</section>
//personalInfo.component.ts
import { Component, OnInit } from '@angular/core';
import { FieldModel } from 'src/app/models/field.model';
import { HttpClient } from '@angular/common/http';
import {map } from 'rxjs/operators';
import { FormGroup } from '@angular/forms';
import { FormControlService } from 'src/app/services/form-control.service';
@Component({
  selector: 'app-personal-info',
  templateUrl: './personal-info.component.html',
  styleUrls: ['./personal-info.component.css']
})
export class PersonalInfoComponent implements OnInit {
  fields: FieldModel[];
  personalInfoForm: FormGroup;
  constructor(private httpClient: HttpClient,
    private formControlService: FormControlService) { }

  ngOnInit() {
    this.getFields();
  }

  getFields(){
    this.httpClient.get('/assets/jsons/fields.json')
    .pipe(map((fields: FieldModel[])=>{
      return fields.map(field=>{
        return new FieldModel(field)
      })
    }))
    .subscribe((fields: any)=>{
      this.fields = fields;
      this.personalInfoForm = this.formControlService.getFormGroupObject(fields);
    });
  }



}

As you can see in the above example, We have a personalInfo component where we are accessing fields object from JSON where we have a configuration for fields and that fields dynamically getting added to formGroup and will render on HTML with proper form validation.

So this is one-time activity to create this dynamic component, after that you need to just create configurable field object and need to pass this component which will take care of this field.

so, here is a final output how it looks like

Above you can look, This form is created with the help of JSON provided above.

If you want to checkout code so, just go through this repository

Conclusion

So, creating a dynamic form is very powerful functionality. which is reusable, big forms can be created on the basis of json only, configurable and reusable also

Leave a Reply

Your email address will not be published. Required fields are marked *