Create Dynamic Forms using Angular

Learn using Angular how to build a simple dynamic form that can be easily extended based on the requirement. We will create dynamic form component that will allow the user to provide configuration object and will render the form according to provided object and will expose the form submit event.

 

In Angular, there are two ways to build forms one is Template-driven and another is Model-driven.

 

Angular allows dynamic rendering capabilities. We can build a dynamic form and configuration driven form component using dynamic rendering capabilities, Let’s see further now…

 

Angular Dynamic Form

 

Introduction

We will build a dynamic form component that will accept the form configuration object and will expose the form submit event.

We’ll capture the form configuration based on below interface:
Error when loading gists from https://gist.github.com/. // dynamic-control.config.ts
export interface DynamicControlConfig {
  type: string;
  name: string;
  label: string;
  placeholder: string;
}

We have considered only basic form controls like text, password, and date in this example. type represents type of the input, name represents name of the form control, label represents label of the input, and so on.

 

Requirements

  1. Create a form based on the form configuration object
  2. Listen on form submit event and get the form data

Application Setup & Implementation

Here is the file structure:

|– app
   |– dynamic-form
      |– dynamic-form-control
         |– dynamic-form-control.component.ts
      |– dynamic-control.config.ts
      |– dynamic-form.component.ts
      |– dynamic-form.module.ts
   |– app.component.html
   |– app.component.ts
   |– app.module.ts

 

To use angular forms, we will have to import FormsModule and ReactiveFormsModule from the package @angular/forms into our DynamicFormModule.

Here is the DynamicFormModule:
Error when loading gists from https://gist.github.com/. // dynamic-form.module.ts
import { DynamicFormComponent } from ‘./dynamic-form.component’;
import { ReactiveFormsModule, FormsModule } from ‘@angular/forms’;
import { DynamicFormControlComponent } from ‘./dynamic-form-control/dynamic-form-control.component’;
import { NgModule } from ‘@angular/core’;
import { CommonModule } from ‘@angular/common’;

@NgModule({
imports: [
CommonModule,
ReactiveFormsModule,
FormsModule
],
  exports: [DynamicFormControlComponent, DynamicFormComponent],
  declarations: [DynamicFormControlComponent, DynamicFormComponent],
  entryComponents: [DynamicFormControlComponent, DynamicFormComponent]
})
export class DynamicFormModule { }

Now that we have created DynamicFormModule, we need to create one component that will take configuration object as an input and will create a form control using that configuration object, Here is the component for that:
Error when loading gists from https://gist.github.com/. // dynamic-form-control.component.ts
import { DynamicControlConfig } from ‘./../dynamic-control.config’;
import { FormGroup } from ‘@angular/forms’;
import { Component } from ‘@angular/core’;
@Component({
  // tslint:disable-next-line:component-selector
  selector: ‘dynamic-form-control’,
  template:
    <div [formGroup]="formGroup" class="form-group">
      <label>{{controlConfig.label}}</label>
      <input [type]="controlConfig.type" class="form-control"
      [placeholder]="controlConfig.placeholder" [formControlName]="controlConfig.name">
    </div>
 

})
export class DynamicFormControlComponent {
  formGroup: FormGroup;
  controlConfig: DynamicControlConfig;
  constructor() { }
}


DynamicFormControlComponent will not be used externally, we will be using it internally to add the form control
dynamically to the form. To add the form control dynamically to the form we will need the dynamic form component
that will accept the configuration object from the user and will create the form accordingly. Here is the code of DynamicFormComponent:
Error when loading gists from https://gist.github.com/. // dynamic-form.component.ts
import { DynamicFormControlComponent } from ‘./dynamic-form-control/dynamic-form-control.component’;
import { DynamicControlConfig } from ‘./dynamic-control.config’;
import { FormGroup, FormControl } from ‘@angular/forms’;
import { Component, OnInit, Input, EventEmitter, Output, ViewChild } from ‘@angular/core’;
import { ViewContainerRef, ComponentFactoryResolver, OnChanges } from ‘@angular/core’;
@Component({
// tslint:disable-next-line:component-selector
selector: ‘dynamic-form’,
template:
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<ng-container #container></ng-container>
<button type="submit" class="btn btn-outline-primary">submit
</button>
</form>

})
export class DynamicFormComponent implements OnChanges {
  _formConfig: DynamicControlConfig[];
  private cfr: ComponentFactoryResolver;
  public form: FormGroup;
  @Input()
  set formConfig(value: any) {
    this._formConfig = value;
  }
  @Output() formSubmit: EventEmitter<any>;
  @ViewChild(‘container’, { read: ViewContainerRef })
  formContainer: ViewContainerRef;
  onSubmit() {
    this.formSubmit.emit(this.form.value);
  }
  constructor(cfr: ComponentFactoryResolver) {
  this.cfr = cfr;
  this.form = new FormGroup({});
  this.formSubmit = new EventEmitter<any>();
  }
  ngOnChanges() {
    if (this._formConfig) {
    this.formContainer.clear();
    this._formConfig.forEach(controlConfig => {
      this.form.addControl(controlConfig.name, new FormControl());
      this.buildControl(controlConfig);
    });
   }
 }
  private buildControl(controlConfig: DynamicControlConfig): void {
    const factory = this.cfr.resolveComponentFactory(DynamicFormControlComponent);
    const control = this.formContainer.createComponent(factory);
    control.instance.controlConfig = controlConfig;
    control.instance.formGroup = this.form;
  }
}

  • We get an array of DynamicControlConfig types through @Input binding.
  • We query the <ng-container> component as a ViewContainerRef . We will use this to attach our dynamically created control components to the template.
  • Inside the constructor, we create an empty FormGroup instance.
  • We implement ngOnChanges() with simple loop that goes through the DynamicControlConfig (_formConfig) array, adding controls to the FormGroup, and call the buildControl method.
  • The buildControl() method is responsible for instantiating the DynamicControlComponent and initializing its properties with controlConfig object and the FormGroup instance.
  • We catch the ngSubmit event and emit a custom event named formSubmit with form data.

Now our component is ready to use, all we need to do is to import the DynamicFormModule into our AppModule, here is our AppModule:
Error when loading gists from https://gist.github.com/. // app.module.ts
import { DynamicFormModule } from ‘./dynamic-form’;
import { BrowserModule } from ‘@angular/platform-browser’;
import { NgModule } from ‘@angular/core’;
import { AppComponent } from ‘./app.component’;
@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    BrowserModule,
    DynamicFormModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

We will use our dynamic form component inside AppComponent, here is AppComponent:
Error when loading gists from https://gist.github.com/.
import { DynamicControlConfig } from ‘./dynamic-form/dynamic-control.config’;
import { Component } from ‘@angular/core’;
@Component({
  selector: ‘app-root’,
  templateUrl: ‘./app.component.html’,
  styleUrls: [‘./app.component.css’]
})
export class AppComponent {
  formConfig: DynamicControlConfig[];
  constructor() {
  this.formConfig = [
    {
        type: ‘text’,
        name: ‘username’,
        label: ‘Username’,
        placeholder: ‘Type username’,
      }, {
        type: ‘password’,
        name: ‘password’,
        label: ‘Password’,
        placeholder: ‘Type password’,
      }
    ];
  }
  onSubmit(event: any) {
    console.log(‘form submitted’, event);
  }
}

Here is the markup of AppComponent:
Error when loading gists from https://gist.github.com/. <div class="container">
  <div class="col-md-6">
    <h3 class="title">Dynamic Form Component</h3>
    <dynamic-form [formConfig]="formConfig" (formSubmit)="onSubmit($event)"></dynamic-form>
  </div>
</div>

 

The output should looks a like below, I’ve used Bootstrap for styling.

When you hit the submit button it’ll log the form data in browser console.

Summary

That’s it, So it is easy to create a simple configuration-driven form, it can be extended to any level based on the requirements. You can add validators to the dynamic component, you can provide custom CSS classes to customize UI of the form, and many other things can be passed in the configuration object.

Happy coding, stay awesome!