In this article, we will learn how to use Angular reactive form and how to validate reactive forms in Angular. We’ll how to validate Angular reactive form using a built-in validator attribute and how to create our own custom Angular form validator.
In this article, we have three goals behind this tutorial. First, we demonstrate how to create Angular reactive form in our Angular project. Secondly, we explore how to implement Angular reactive form validation. At last, create our own custom validator class. Let’s get started.
What is an Angular reactive form?
In the Angular reactive form, the developer builds all the control and logic of validation of the form in the component typescript file. The developer has absolute control over how and when the data is synced from the UI to the model and vice versa.
Angular provides two approaches to building forms, Angular reactive form, and template form. In previous we have learned, that the template drives forms, where most of our logic, validation, and controls are in template or HTML form.
We do not configure Angular form in the template, we are only synchronizing it with the directives, form control name, and formGroup. In this approach, we create a form from the instance of FormGroup. The FormGroup instance allows us to specify form controls and the various validations on the input element of the form. The reactive form is the best choice for creating a complex form, it provides the developer with more control over the form.
How to use Angular reactive form?
We will do this step by step, creating our Angular project, configuring the Reactive form setup, and adding the building block of the reactive form. From Form Controls, we will work on various components like Form Groups and Form Builders. Here are abstract steps for creating a reactive form.
- Create an Angular project.
- Import ReactiveFormsModule in our application.
- Create a form model or object in our component and add a form controller class.
- Create a template for the reactive form.
- Add Angular reactive forms validation.
ng new angularReactiveFormExample
cd angularReactiveFormExample
Import reactive form module in app.module.ts file
To start using Angular reactive form we need to import ReactiveFormsModule in the app.module.ts file. This module contains all the tools we need now to build our own form.
import { ReactiveFormsModule } from '@angular/forms';
imports: [
ReactiveFormsModule
],
To demonstrate reactive form we are creating an interface called IDepartment and creating this file in app/model/department.model.ts
export interface IDepartment{
id:number,
name: string,
description: string
}
We also need data for the form select field, let’s create an array of data containing information about departments in assets/data/mockDepartment.ts and add the following data for the departments’ array.
import { IDepartment } from "src/app/model/department.model";
export const MockDepartment : IDepartment[] = [
{
id: 0,
name: 'Designing',
description: 'Photoshop, Indesign'
},
{
id: 1,
name: 'Web Developer',
description: 'Web through Node and Angular'
},
{
id: 3,
name: 'Hybrid App Developer',
description: 'Through Ionic'
},
{
id: 3,
name: 'Android Developer',
description: 'The 140 Android App developed'
},
{
id: 4,
name: 'IOS Developer',
description: 'Lastest ISO'
}
]
Angular reactive form example
Once we have created our form project and imported ReactiveFormsModule. We will demonstrate the Angular reactive form example and here we have a screenshot of our Angular reactive form demo.
The core of any reactive form is the FormControl, which directly represents an individual form element of a form into one FormGroup that represents form. Thus Angular reactive form is nothing but a set of grouped FormControls. Grouping form control into a single FormGroup allows us to easily validate, reset and check from states.
The reactive form we should initialize before rendering the template of the component so define form setup in ngOnInit() method.
studentForm = new FormGroup({});
constructor(){}
ngOnInit() {
this.studentForm = this.formBuilder.group({
name : '',
email: '',
..
OR
'number': [null, Validators.required],
'description': [null, [Validators.required, Validators.minLength(10)]],
});
}
In reactive form allow us to group elements into a group and we will group both name and email fields into a FormGroup called Student.
The FormGroup contains FromControls (rendered as key/value pairs) which are used to target each individual input field in our form. The formControls first argument is the default value of the control element and the second argument is the validator can be a single or an array of validators we want to apply to this control element. The third argument will be potential asynchronous validators. Let’s add our Angular reactive form example code in the app component controller and template.
import { Component } from '@angular/core';
import { FormGroup, FormBuilder, Validators, NgForm } from '@angular/forms';
import { MockDepartment } from 'src/assets/data/mockDepartment';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title: string = 'Angular Reactive Form';
form = new FormGroup({});
departments: any[] = [];
constructor(private formBuilder: FormBuilder) { }
ngOnInit() {
this.form = this.formBuilder.group({
number : '',
description: '',
sinceFrom: '',
category: '',
classType: '',
});
this.departments = MockDepartment;
}
submit(form: NgForm) {
console.log(form);
}
}
We have to import the formGroup and FormBuilder from @angular/forms in our component.
FormGroup
A FormGroup aggregates the values of each child FormControl into one object, with each control name as the key. It calculates its status by reducing the statuses of its children. For example, if one of the controls in a group is invalid, as a result, the entire group becomes invalid. The FormGroup is useful as a way to group relevant form fields under one group. This gives us the convenience of whether we want to track the form controls individually, or as a group.
FormBuilder
Angular has a helper Class called FormBuilder to help us to build a form model with less code. FormBuilder allows us to explicitly declare forms in our components. This allows us to also explicitly list each form control’s validators. FormBuilder has methods as group(), control() and array() that returns FormGroup, FormControl and FormArray respectively. Using FormBuilder we can directly pass the object or array of objects of a class to create the form.
In the app.component.html file add our form HTML code.
<div class="container">
<h4> Angular reactive form Example</h4>
<div class="card">
<div class="card-block">
<form [formGroup]="form" (ngSubmit)="submit(form.value)">
<div class="form-group">
<label>Name of students:</label>
<input type="number" class="form-control" formControlName="number" />
</div>
<div class="form-group">
<label>Department:</label>
<select class="form-control" formControlName="category">
<option *ngFor="let item of departments" value="{{item.name}}">{{item.name}}</option>
</select>
</div>
<div class="form-group">
<label>Department Started on:</label>
<input type="date" class="form-control" formControlName="sinceFrom" />
</div>
<div class="form-group">
<label>Description :</label>
<textarea class="form-control" formControlName="description"></textarea>
</div>
<div class="form-group">
<label>Class on</label>
<input type="radio" name="classType" class="form-control" value="Day class" formControlName="classType">Day Class
<input type="radio" name="classType" class="form-control" value="Evening" formControlName="classType"> Evening Class
</div>
<button class="btn btn-primary btn-block">Submit</button>
</form>
</div>
<p>Form Element : {{ form.value | json}}</p>
</div>
</div><!-- End of container div -->
<router-outlet></router-outlet>
For UI of this form, we have used Bootstrap from ng-bootstrap, if you want to use this UI, then add bootstrap to our angular project. We used the formGroup directive in the reactive form to link with our particular form from component typescript.
ngSubmit:
The ngSubmit binding handles the form submits an event.
We have learned template forms are mutable, on another hand Angular reactive forms are immutable. Every change to the form creates a new state of the form and maintains the integrity of the model in the component.
Angular reactive forms validation
The FormBuilder class allows us to initialize and add a validator for each FormControl. Here is a simple demo example of how we can different validators on the reactive form control elements.
ngOnInit() {
this.form = this.fb.group({
name: [null, [Validators.required, Validators.minLength(5)]],
dob: [null, [Validators.required]],
email: [null, [Validators.required,
Validators.pattern("^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$")]],
password: [null, [Validators.required, Validators.minLength(6)]],
});
Here is a screenshot of our Angular reactive forms validation.
Let’s edit the app.component.ts file to add validation code for our example.
import { Component } from '@angular/core';
import { FormGroup, FormBuilder, Validators, NgForm } from '@angular/forms';
import { MockDepartment } from 'src/assets/data/mockDepartment';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title: string = 'Angular Reactive Form';
form = new FormGroup({});
departments: any[] = [];
constructor(private formBuilder: FormBuilder) { }
ngOnInit() {
this.form = this.formBuilder.group({
'number': [null, Validators.required],
'description': [null, [Validators.required,
Validators.minLength(10), Validators.maxLength(15)]],
'sinceFrom': [''],
'category': [null, Validators.required],
'classType': [],
});
this.departments = MockDepartment;
}
submit(form: NgForm) {
console.log(form);
}
}
Displaying error message for form element in the template:
The FormArray class is a way to manage the collection of Form controls in Angular. The controls can be a FormGroup, a FormControl, or another FormArray. Access the FormArray control, we can access form control in two ways in the component template.
- First by using the get() method we can easily access the form array instance.
- Using form control to check form element validation. If any error occurs then display an error message for each form element error related to validation. An example is to check email form control validation.
<span *ngIf="!form.get('email').valid && form.get('email').touched">
Please enter a valid email!
</span>
Now lets, edit our previous form in the app.component.html to add a check and add validation message.
<div class="container">
<h4> Angular reactive form Example</h4>
<div class="card">
<div class="card-block">
<form [formGroup]="form" (ngSubmit)="submit(form.value)">
<div class="form-group">
<label>Name of students:</label>
<input type="number" class="form-control" formControlName="number" />
<div class="alert alert-danger" *ngIf="!form.controls['number'].valid && form.controls['number'].touched">
Total number of student is required.
</div>
</div>
<div class="form-group">
<label>Department:</label>
<select class="form-control" formControlName="category">
<option *ngFor="let item of departments" value="{{item.name}}">{{item.name}}</option>
</select>
<div class="alert alert-danger"
*ngIf="!form.controls['category'].valid && form.controls['category'].touched">
Department is required
</div>
</div>
<div class="form-group">
<label>Department Started on:</label>
<input type="date" class="form-control" formControlName="sinceFrom" />
</div>
<div class="form-group">
<label>Description :</label>
<textarea class="form-control" formControlName="description"></textarea>
<div class="alert alert-danger"
*ngIf="!form.controls['description'].valid && form.controls['description'].touched">
Description should be of minimum 10 and maximun 50 characters
</div>
</div>
<div class="form-group">
<label>Class on</label>
<input type="radio" name="classType" class="form-control" value="Day class" formControlName="classType">Day
Class
<input type="radio" name="classType" class="form-control" value="Evening" formControlName="classType"> Evening
Class
</div>
<button class="btn btn-primary btn-block">Submit</button>
</form>
</div>
<p>Form Element : {{ form.value | json}}</p>
</div>
</div>
<router-outlet></router-outlet>
The Built-in Angular Validation Attributes for Angular reactive form validation.
- required: This attribute is used to specify a value that must be provided.
- minlength: This attribute is used to specify a minimum number of characters.
- maxlength: This attribute is used to specify a maximum number of characters. This type of validation cannot be applied directly to form elements because it conflicts with the HTML5 attribute of the same name. It can be used with model-based forms.
- pattern: This attribute is used to specify a regular expression that the value provided by the user must match.
Styling Elements Using Validation Classes in angular reactive form validation
The underlying Angular framework detects the validation state of the input field and applies its own specific classes to reflect those states. The classes to which an input element is assigned provide details of its validation state as.
- ng-dirty: Input field had interacted with.
- ng-touched: Input field has received focus.
- ng-valid: Data entry has passed validation.
- ng-invalid: Data entry has not passed validation)
- ng-pristine: An element if its contents have not been changed.
We can apply the red border on the input of the invalid form element by adding the following CSS style in the app.component.scss as.
input.ng-invalid.ng-touched {
border: 1px solid red;
}
We can also assign our custom validation methods from our own custom Validator service.
How to add our own custom validator in Angular reactive form?
In the previous example, we had implemented an Angular reactive form validator from the Validators class of the Angular form module. We can add our own custom validation on the Angular form. Let’s create a service called CustomValidatorService and create a component called custom-validator-form to implement our custom validation on Angular form.
ng generate component components/customValidatorForm
ng generate service services/customValidator
In our app/services/custom-validator.service.ts file add custom validator code to check the following validation.
- To check if any number is added to the name text field, if so then show an error.
- To check valid email or not.
Here is a screenshot of our Angular custom validation on our form.
import { Injectable } from '@angular/core';
import { FormControl } from '@angular/forms';
@Injectable({ providedIn: 'root' })
export class CustomValidatorService {
constructor() { }
emailValid(control: FormControl) {
return new Promise(resolve => {
const emailPattern = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;
if (!emailPattern.test(control.value)) {
resolve({ invalidEmail: true });
}
resolve(null);
});
}
nameValid(control: FormControl) {
return new Promise(resolve => {
const pattern = /[0-9]/;
if (pattern.test(control.value)) {
resolve({ invalidName: true });
}
resolve(null);
});
}
}
In our custom-validator-form.component.ts file let’s add our custom validation class.
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validator, Validators, NgForm } from '@angular/forms';
import { CustomValidatorService } from 'src/app/services/custom-validator.service';
@Component({
selector: 'app-custom-validator-form',
templateUrl: './custom-validator-form.component.html',
styleUrls: ['./custom-validator-form.component.scss']
})
export class CustomValidatorFormComponent implements OnInit {
form = new FormGroup({});
constructor(private fb: FormBuilder,
private customValidator: CustomValidatorService) {}
ngOnInit() {
this.form = this.fb.group({
name: ['', Validators.required, this.customValidator.nameValid],
email: ['', Validators.required, this.customValidator.emailValid],
platform: ['', Validators.required]
});
const name = this.form.controls.name;
name.valueChanges.subscribe((value: string) => {
console.log(`Entered name is ${value}`);
});
}
submit(form: NgForm) {
console.dir(form);
}
}
In our custom-validator-form.component.html let us add form and add validation message on our form input.
<div class="container">
<h4> Angular custom validator for reactive form</h4>
<div class="card">
<div class="card-block">
<form [formGroup]="form" (ngSubmit)="submit(form.value)">
<div class="form-group">
<label>Name of students:</label>
<input formControlName="name" class="form-control" />
<div *ngIf="form.controls['name'].dirty && !form.controls['name'].valid"
class="text-danger font-weight-bold">
<p *ngIf="form.controls['name'].errors?.invalidName">
Your name cannot contain any numbers.
</p>
</div>
</div>
<div class="form-group">
<label>Enter your email</label>
<input type="email" formControlName="email" class=" form-control" />
<div *ngIf="form.controls['email'].dirty && !form.controls['email'].valid"
class="text-danger font-weight-bold">
<p *ngIf="(form.controls['email']).errors?.invalidEmail">
You must enter a valid e-mail address.
</p>
</div>
</div>
<div class="form-group">
<label for="platform">Favourite Platform:</label>
<select class="form-control" name="platform" id="platform" formControlName="platform">
<option value="android">Android</option>
<option value="ios">IOS</option>
<option value="window">Window</option>
</select>
</div>
<button class="btn btn-primary btn-block" [disabled]="!form.valid">Submit</button>
</form>
</div>
</div>
Conclusion
We have finished the Angular reactive form example with how to validate the reactive form. Reactive forms have more control, perfect for more advanced forms, enable unit testing, and are easy to manage. We had uploaded our Angular reactive form code to the Github repository if you want and you can check it.
Related topic
- How to implement Angular material form in Angular 11 | 12?
- How to use angular reactive form validation?
- How to fix Firebase storage CORS issues in Angular and other frontend frameworks.