Angular 8 - Dynamic Reactive Forms Example
Example built with Angular 8.0.2
Other versions available:
- Angular: Angular 14, 10, 9
- React: React Hook Form 7, 6, React + Formik
- Vue: Vue + Vuelidate
This is a quick example of how to build a dynamic form with validation in Angular 8 using Reactive Forms. The example is a simple order form for selecting the number of tickets to purchase and entering a name and email address for each ticket. All fields are required and email fields must contain a valid email address.
I've setup the form to validate on submit instead of on field change, this is implemented with a submitted
property in the app component that is set to true
when the form is submitted for the first time, and reset to false
if the reset or clear button is clicked.
The "Buy Tickets" button displays the form values in an alert popup if the form is valid. The "Reset" button resets the form back to it's initial state including the removal of all ticket name & email fields. The "Clear" button clears the values of ticket name & email fields.
Styling of the example is all done with Bootstrap 4.3 CSS.
Here it is in action: (See on StackBlitz at https://stackblitz.com/edit/angular-8-dynamic-reactive-forms-example)
Dynamic Reactive Forms App Component
The app component defines the form fields and validators for the dynamic form using an Angular FormBuilder
to create an instance of a FormGroup
that is stored in the dynamicForm
property. The dynamicForm
is then bound to the form in the app template below using the [formGroup]
directive.
The dynamic form contains two properties:
numberOfTickets
is an AngularFormControl
that stores the number of tickets selected. It is bound to theselect
input in the app component template with the directiveformControlName="numberOfTickets"
.tickets
is an AngularFormArray
used to hold an array of form groups (FormGroup
) for storing ticket holder details. Each ticket form group contains aFormControl
for thename
andemail
of the ticket holder.
The f
and t
getters are convenience properties to make it easier to access form controls from the template. So for example you can access the numberOfTickets
field in the template using f.numberOfTickets
instead of dynamicForm.controls.numberOfTickets
.
The onChangeTickets()
method dynamically adds or removes ticket forms from the tickets
form array when the number of tickets selected is increased or decreased.
The onSubmit()
method sets the submitted
property to true to show validation messages, checks if the form is valid and displays the form values in an alert popup if it is.
The onReset()
method resets the submitted
property to false to hide validation messages, clears all form values with this.dynamicForm.reset()
, and removes ticket name & email fields with this.t.clear()
.
The onClear()
method resets the submitted
property to false to hide validation messages, and clears the values of ticket name & email fields with this.t.reset()
.
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, FormArray, Validators } from '@angular/forms';
@Component({ selector: 'app', templateUrl: 'app.component.html' })
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([])
});
}
// convenience getters for easy access to form fields
get f() { return this.dynamicForm.controls; }
get t() { return this.f.tickets as FormArray; }
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;
// stop here if form is invalid
if (this.dynamicForm.invalid) {
return;
}
// display form values on success
alert('SUCCESS!! :-)\n\n' + JSON.stringify(this.dynamicForm.value, null, 4));
}
onReset() {
// reset whole form back to initial state
this.submitted = false;
this.dynamicForm.reset();
this.t.clear();
}
onClear() {
// clear errors and reset ticket fields
this.submitted = false;
this.t.reset();
}
}
Dynamic Reactive Forms App Template
The app component template contains the html and angular template syntax for displaying the example dynamic form in your browser. The form element uses the [formGroup]
directive to bind to the dynamicForm
FormGroup in the app component above.
A nested form group with name and email fields is rendered for each ticket by looping over the tickets form array with the Angular *ngFor
directive. Each ticket form group is bound to a containing div element with the directive [formGroup]="ticket"
.
The form binds the form submit event to the onSubmit()
handler in the app component using the Angular event binding (ngSubmit)="onSubmit()"
. Validation messages are displayed only after the user attempts to submit the form for the first time, this is controlled with the submitted
property of the app component.
The reset button click event is bound to the onReset()
handler in the app component using the Angular event binding (click)="onReset()"
, and the clear button is bound to the onClear()
handler with (click)="onClear()"
.
<form [formGroup]="dynamicForm" (ngSubmit)="onSubmit()">
<div class="card m-3">
<h5 class="card-header">Angular 8 Dynamic Reactive Forms Example</h5>
<div class="card-body">
<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=""></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 t.controls; 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">Email must be a valid email address</div>
</div>
</div>
</div>
</div>
</div>
<div class="card-footer text-center">
<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>
Dynamic Reactive Forms App Module
There isn't much going on in the app module other than the standard stuff, the main thing you need to remember for using reactive forms in Angular is to import the ReactiveFormsModule from '@angular/forms'
and include it in the imports array of the @NgModule
decorator.
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
@NgModule({
imports: [
BrowserModule,
ReactiveFormsModule
],
declarations: [
AppComponent
],
bootstrap: [AppComponent]
})
export class AppModule { }
Need Some Angular 8 Help?
Search fiverr for freelance Angular 8 developers.
Follow me for updates
When I'm not coding...
Me and Tina are on a motorcycle adventure around Australia.
Come along for the ride!