Explain Angular dynamic forms

What are dynamic forms?

Dynamic forms are kind of forms that change in real-time (dynamically) as users fill them out. These forms guide the user through the steps needed to finish a form. With fine performance, Angular offers fine building accessibility as well.

Dynamic forms are a pattern in which we tend to build a form-supported meta description, and we use the reactive form API to achieve it.

Many forms can be very similar to each other in format and intent like questionnaires. You can create a dynamic form template based on metadata that describes the business object model to create this faster and easier. You’ll be to use the template to generate new forms dynamically according to changes in the data model.

This technique is especially helpful when you have a type whose content must change frequently to meet rapidly changing business and regulatory requirements. Dynamic forms’ typical use is a questionnaire. We may need to get input from users in a different context. The format and style of the forms a user will see should remain constant, but the actual questions you need to ask to vary with context.

Example

We have created an example of how to build a dynamic form with validation in Angular using Reactive forms. In this form, the user is allowed to select the number of tickets to purchase and enter the name and email of each person, both name and email fields are required and email must contain a valid email address.

The validations are set up on submit instead of on-field change which is implemented with a submitted property in the app component that is set to true if the form is submitted for the first time and it is reset to false when the reset or clear button is clicked.

Clicking on the “Buy Tickets” button will display an alert with form values if the form is valid. The “Reset” button will remove all the ticket names and email fields and reset the form back to its initial state. Clicking on the “Clear” button will clear the values of ticket name and email fields but it will leave the number of tickets selected.

Dynamic forms are based on reactive forms so we need to import ReactiveFormsModule from the @angular/forms library and include it in the imports array of the @NgModule decorator in the app.module.ts file to give the application access to reactive forms directives. 

app.module.ts:

import { NgModule } from ‘@angular/core’;

import { BrowserModule } from ‘@angular/platform-browser’;

import { ReactiveFormsModule } from ‘@angular/forms’;

import { AppComponent } from ‘./app.component’;

@NgModule({

  declarations: [

    AppComponent

  ],

  imports: [

    BrowserModule,

    ReactiveFormsModule

  ],

  providers: [],

  bootstrap: [AppComponent]

})

export class AppModule { }

The app.component.html template contains the angular template syntax to display the dynamic form in the browser. The [formGroup] directive is used by the form element to bind to the dynamicForm FormGroup.

We will create a nested form group and render name and email fields for each ticket by looping over the tickets from an array using the Angular *ngFor directive. Every ticket form group is bound to a containing div element using the directive [formGroup]=”ticket”.

Using the Angular event binding (ngSubmit)=”onSubmit()”, we will bind the form to submit an event to the event handler. After the user attempts to submit the form for the first time, validation messages are displayed if the fields are not filled with valid data and this is controlled by the submitted property.

Using the Angular event binding (click)= “onReset()”, we will bind the reset button click event to the onReset() handler. And we will bind the clear button to the onClear() handler with (click)=”onClear()”.

app.component.html:

<div style=”width: 600px;align-self: center;margin-left: 500px;”>

<form [formGroup]=”dynamicForm” (ngSubmit)=”onSubmit()”>

  <div class=”card m-3″ style=”background-color: whitesmoke;”>

      <h5 class=”card-header”>Angular Dynamic Form</h5>

      <div class=”card-body border-bottom”>

          <div class=”form-row”>

              <div class=”form-group”>

                  <label>Number of Tickets</label>

                  <select formControlName=”numberOfTickets” class=”form-control” (change)=”onChangeTickets($event)” [ngClass]=”{ ‘is-invalid’: submitted && f.numberOfTickets.errors }”>

                      <option value=””>Select</option>

                      <option *ngFor=”let i of [1,2,3,4,5,6,7,8,9,10]”>{{i}}</option>

                  </select>

                  <div *ngIf=”submitted && f.numberOfTickets.errors” class=”invalid-feedback”>

                      <div *ngIf=”f.numberOfTickets.errors.required”>Number of tickets is required</div>

                  </div>

              </div>

          </div>

      </div>

      <div *ngFor=”let ticket of ticketFormGroups; let i = index” class=”list-group list-group-flush”>

          <div class=”list-group-item”>

              <h5 class=”card-title”>Ticket {{i + 1}}</h5>

              <div [formGroup]=”ticket” class=”form-row”>

                  <div class=”form-group col-6″>

                      <label>Name</label>

                      <input type=”text” formControlName=”name” class=”form-control” [ngClass]=”{ ‘is-invalid’: submitted && ticket.controls.name.errors }” />

                      <div *ngIf=”submitted && ticket.controls.name.errors” class=”invalid-feedback”>

                          <div *ngIf=”ticket.controls.name.errors.required”>Name is required</div>

                      </div>

                  </div>

                  <div class=”form-group col-6″>

                      <label>Email</label>

                      <input type=”text” formControlName=”email” class=”form-control” [ngClass]=”{ ‘is-invalid’: submitted && ticket.controls.email.errors }” />

                      <div *ngIf=”submitted && ticket.controls.email.errors” class=”invalid-feedback”>

                          <div *ngIf=”ticket.controls.email.errors.required”>Email is required</div>

                          <div *ngIf=”ticket.controls.email.errors.email”>Please enter a valid email address</div>

                      </div>

                  </div>

              </div>

          </div>

      </div>

      <div class=”card-footer text-center border-top-0″>

          <button class=”btn btn-primary mr-1″>Buy Tickets</button>

          <button class=”btn btn-secondary mr-1″ type=”reset” (click)=”onReset()”>Reset</button>

          <button class=”btn btn-secondary” type=”button” (click)=”onClear()”>Clear</button>

      </div>

  </div>

</form>

</div>

The app.component.ts defines the form fields and validators for the dynamic form using an Angular FormBuilder to make an instance of a FormGroup  stored within the dynamicForm property. The dynamicForm is then bound to the <form> element in the app template below using the [formGroup] directive.

The dynamic form FormGroup contains the following two form controls:

  • numberOfTickets is an Angular FormControl for storing the number of tickets selected. This is bound to the select input in the app component template with the formControlName=”numberOfTickets” directive.
  • tickets is an Angular FormArray for holding an array of form groups (FormGroup) to store ticket holder details. Each ticket form group contains two child form controls, one for the name and one for the ticket holder’s email.

The f() and t() getters are convenience properties for making it easier to access form controls from the template. So, you will be able to access the numberOfTickets field within the template using f.numberOfTickets rather than dynamicForm.controls.numberOfTickets.

The ticketFormGroups() getter is used by the template for rendering the formgroup for each ticket, this getter is required when using AOT compilation as the template requires a strongly typed FormGroup object to bind with the [formGroup] directive.

When the number of tickets selected is changed, the onChangeTickets() method will dynamically add or remove ticket forms from the ticket form array according to the number of tickets selected.

The onSubmit() method is used to set the submitted property to true to show validation messages, it also checks if the form is valid and displays the values of the form in an alert if the form is valid.

The onReset() method is used to reset the submitted property to false to hide the validation messages, to clear all the form values with this.dynamicForm.reset() method, and to remove ticket name and email fields with this.t.clear() method.

The onClear() method is used to reset the submitted property to false to hide the validation messages, and to clear the values of ticket name email fields with this.t.reset() method.

app.component.ts:

import { Component, OnInit } from ‘@angular/core’;

import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from ‘@angular/forms’;

@Component({

  selector: ‘app-root’,

  templateUrl: ‘./app.component.html’,

  styleUrls: [‘./app.component.css’]

})

export class AppComponent implements OnInit {

  dynamicForm!: FormGroup;

  submitted = false;

  constructor(private formBuilder: FormBuilder) { }

  ngOnInit() {

    this.dynamicForm = this.formBuilder.group({

      numberOfTickets: [”, Validators.required],

      tickets: new FormArray([])

    });

  }

  get f() { return this.dynamicForm.controls; }

  get t() { return this.f.tickets as FormArray; }

  get ticketFormGroups() { return this.t.controls as FormGroup[]; }

  onChangeTickets(e) {

    const numberOfTickets = e.target.value || 0;

    if (this.t.length < numberOfTickets) {

      for (let i = this.t.length; i < numberOfTickets; i++) {

        this.t.push(this.formBuilder.group({

          name: [”, Validators.required],

          email: [”, [Validators.required, Validators.email]]

        }));

      }

    } else {

      for (let i = this.t.length; i >= numberOfTickets; i–) {

        this.t.removeAt(i);

      }

    }

  }

  onSubmit() {

    this.submitted = true;

    if (this.dynamicForm.invalid) {

      return;

    }

    alert(‘SUCCESS!! :-)\n\n’ + JSON.stringify(this.dynamicForm.value, null, 3));

  }

  onReset() {

    this.submitted = false;

    this.dynamicForm.reset();

    this.t.clear();

  }

  onClear() {

    this.submitted = false;

    this.t.reset();

  }

}

The output of the example:

When we run the project using the ng serve –open command, it will display the following output which includes a form having only one dropdown list for selecting the number of tickets and three buttons.

Figure 1 Output

    Now, we will select the number of tickets from the dropdown list, we can select between the range of 1 to 10 tickets.

Figure 2 Output

    After selecting 2 tickets as shown in the above image, 2 dynamic forms are generated automatically with ticket name and email fields each as shown in the following image.

Figure 3 Output

    If we select 4 tickets, it will generate 4 dynamic forms with name and email fields in each form as shown in the following image.

Figure 4 Output 

Figure 5 Output

After filling the form and clicking on the “Buy Tickets” button, an alert popup is displayed including the value of the form.

Figure 6 Output

When we click on the “Clear” button, the values filled in the form field are removed.

Figure 8 Output

Conclusion

  • Dynamic forms are based on Angular Reactive forms. Dynamic forms are useful in the cases when there is a need to generate a form according to values the user fills out for example questionnaire.
  • We have seen another example when there is a need to create dynamic forms as the user selects the number of tickets to purchase.

Author Bio: Ajay Patel – Technical Director, iFour Technolab Pvt. Ltd.

A Seasoned technocrat with years of experience building technical solutions for various industries using Microsoft technologies. With sharp understanding and technical acumen, have delivered hundreds of Web, Cloud, Desktop and Mobile solutions and is heading the technical department at iFour Technolab Pvt. Ltd., an incredible Excel Addin Development Company that provides sustainable solutions.

By Anurag Rathod

Anurag Rathod is an Editor of Appclonescript.com, who is passionate for app-based startup solutions and on-demand business ideas. He believes in spreading tech trends. He is an avid reader and loves thinking out of the box to promote new technologies.