Angular Reactive Form, including Angular Material and Custom Validation

Hossein Mousavi
Angular In Depth
Published in
6 min readMay 8, 2020

Forms are significant parts of every Angular project, and in this article, we want to implement a Reactive Angular form with a custom and dynamic validator.

Photo by Blake Connally on Unsplash

Prepare an Angular project with Angular Material and Bootstrap using Angular CLI

First, we need to initialize an Angular project and add bootstrap and Angular Material to the project. All can be done using Angular-CLI like this:

#for creating an angular project:
ng new angular-forms
#for adding bootstrap
npm install --save bootstrap
#for adding Angular Material
ng add @angular/material

Note that you need to make sure that you have both bootstrap and angular materials in yourangular.jsonfile like this:

"styles": [
"./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css",
"./node_modules/bootstrap/dist/css/bootstrap.css",
"src/styles.css"
]

As we are going to use angular Material, we need to import some of its modules from @angular/material and add them in import array in NgModule here we need the following modules.

import { MatRadioModule, MatCardModule, MatInputModule, MatButtonModule }
from "@angular/material";

Make sure that you add them in importarray as well.

Get started with Angular Forms

After everything has been set up, we can start creating our angular form. Note that we have two approaches for creating an angular form:

  1. Template-Driven Approach: here, we set up the whole form in the HTML file, and with local references, we can work with data in the TypeScript file.
  2. Reactive Form Approach: we create a form in TypeScript and then use it in HTML. We choose this approach here.

First, we need to import angular forms modules in app.module.ts :

import { FormsModule, ReactiveFormsModule } from "@angular/forms";
// make sure to add it in imports array of @NgModule:
@NgModule({
declarations: [AppComponent],
imports:[
BrowserModule, BrowserAnimationsModule, FormsModule,
ReactiveFormsModule, MatRadioModule, MatCardModule,
MatInputModule, MatButtonModule,],
providers: [],
)}

keep that in mind for Reactive Form Approach, we must import ReactiveFormsModule but for Template-Driven approach it is not required.

In the app.component.ts file, we need to import two modules from @angular/forms. We need these modules for building forms and validating them. Also, make sure to create an instance of FormBuilder in the constructor. At this point, your app.component.ts should look like this:

import { Component } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
@Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"],
})
export class AppComponent {
constructor(private formBuilder: FormBuilder) {}
}

Now we can create our first angular form. For this, assume we want to create an employment agency website's registration page. You have two roles: you are either looking for a job (jobSeeker), or you want to hire someone (employer), so the form should look like this:

registerForm: FormGroup = this.formBuilder.group({
fullName: [, { validators: [Validators.required], updateOn: "change" }],
email: [
,{validators: [Validators.required, Validators.email]
,updateOn: "change",}],
password: [, { validators: [Validators.required], updateOn: "change" }],
phone: [, { updateOn: "change" }],
role: ['jobSeeker', { validators: [Validators.required], updateOn: 'change' }]
});

So let's check the terminology of the form above: as you can see, the type of registerForm is FormGroup (which needs to be imported from @angular/forms) the whole registerForm is a form group, and each of the elements within it is a form-control. We can validate the form group, submit it, or add another form control to it. As you can see, we have not set any validator to phone as we want to add a custom and dynamic validator later. We also have not declared a default value to any of the form controls above except role.

Now let's work on the HTML. We want to have a radio button for selecting a role and some input fields for the other form controls.

<form [formGroup]="registerForm" (submit)="submitForm()">
<mat-radio-group name="account_type" formControlName="role">
<label class="mx-3">Select Your Role: </label>
<mat-radio-button class="mx-2" value="jobSeeker">
Job Seeker
</mat-radio-button>
<mat-radio-button class="mx-2" value="employee">
Employee
</mat-radio-button>
</mat-radio-group>
<form>

As you can see, we have declared registerForm as the formGroup of <form> and for formControls, we declared their name with formControlName . We have also declared the value of each radio button. So let's see the email input.

<mat-form-field class="col-5 mb-2 mx-auto">
<mat-label>Email</mat-label>

<input type="email" matInput formControlName="email"
placeholder="Ex. mail@gmail.com"/>

<mat-error *ngIf="
registerForm.get('email').hasError('email')
&&!registerForm.get('email').hasError('required')"
>
Please enter a valid email address
</mat-error>

<mat-error *ngIf="registerForm.get('email').hasError('required')">
Email is <strong>required</strong>
</mat-error>
</mat-form-field>

As we have set a validator to validate email, we have declared two error conditions here which are responsible for showing a proper message:

  1. Check Required Email: for this, we first need to reach the email form control using registerForm.get('email') and then use a method called hasError() on it to check its validity.
  2. Check Valid Email: Like the above, this needs to check both required and validity to ensure the user has written the correct email format.

We have the same thing for the password and full name as well:

<!-- full name -->
<mat-form-field class="col-5 mt-3 mx-auto">
<mat-label>Full Name</mat-label>

<input type="text" matInput formControlName="fullName"
placeholder="Ex. Hossein Mousavi"/>

<mat-error
ngIf="registerForm.get('fullName').hasError('required')">
Full Name is <strong>required</strong>
</mat-error>

</mat-form-field>
<!-- password -->
<mat-form-field class="col-5 mb-2 mx-auto">
<mat-label>Password</mat-label>

<input type="password" matInput formControlName="password" />

<mat-error
ngIf="registerForm.get('password').hasError('required')">
Password is <strong>required</strong>
</mat-error>
</mat-form-field>

Custom validation

Here we want to create a custom validation that works dynamically. When the user selects JobSeeker as their role, we need to make sure a Phone Number is required as well, but for Employees, we want this to be optional. So we need to declare a function for that and update the validator of the form. Note that although the phone number is optional for the employee, we need to make sure that they enter the correct phone number so we have validation for that role as well.

private setPhoneValidation(): void {
const phoneControl = this.registerForm.get("phone");
phoneControl.setValidators(
[Validators.pattern("^[0-9]*$"), Validators.required,]);

this.registerForm.get("role").valueChanges.subscribe((role) => {
if (role == "jobSeeker") {
phoneControl.setValidators(
[Validators.pattern("^[0-9]*$"), Validators.required,]);
} else if (role == "employee") {
phoneControl.setValidators([Validators.pattern("^[0-9]*$")]);
}
phoneControl.updateValueAndValidity();
});
}

keep that in mind The setValidators() method, clears all of the previous validators and that is why we are not declaring the regex for number validation, in the formControl and set required on role changes.

The reason we have set validators in the first lines of the function is that we are subscribing to the role for its changes, and the default role is jobSeeker, so we need to make sure it has its validators at the beginning when the default role is jobSeeker, and we have not changed the role yet.

For the setPhoneValidation() function to work correctly, make sure that you are calling it in the ngOnInit() .

ngOnInit() {
this.setPhoneValidation();
}

We can also disable the submit button as long as the form is invalid like this:

<button
[disabled]="!registerForm.valid"
mat-flat-button color="primary"
type="submit">
Submit
</button>

Here is a picture of our final output:

Final output

So just like that, we have implemented a Reactive Angular Form with Angular Material and Bootstrap and declared our own validator function.

--

--