Reactive forms validation in Angular
Angular is a framework which makes the web applications development easier. Recently I have been exploring the Angular to build a single page application(SPA). When doing so, I came across a need to build a form which takes the user details and contact the server back-end to save the details. In this post, I will share the form validation techniques that I have learned when building the reactive forms using Angular 4.
Also, we will see how to do the confirm password validation using the reactive angular forms. Before going into the details, let’s have a brief look into the different form-building techniques provided by Angular.
Template-driven forms
In template-driven approach, the forms can be built by writing templates in the Angular template syntax with the form-specific directives. FormsModule have to imported into the app. module. Also, FormsModule
need to be added to the list of imports defined in the @NgModule
decorator. Below listing shows the sample app.module.ts
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; @NgModule({ imports: [ BrowserModule, FormsModule ], declarations: [ AppComponent ], providers: [ ], bootstrap: [ AppComponent ] }) export class AppModule { } |
Then in the HTML template for the component, declare the form element as shown below.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<input type="text" id="userid" name="userId" required minlength="6" maxlength="10" pattern="[a-zA-Z0-9]*" [(ngModel)]="model.userid" #userid="ngModel" class="form-control form-control-lg" placeholder="User ID"> <div *ngIf="!userid.valid && userid.touched" class="alert alert-danger"> <div *ngIf="userid.errors.required">User ID is required</div> <div *ngIf="userid.errors.minlength">User ID should be minimum {{userid.errors.minlength.requiredLength}} characters</div> <div *ngIf="userid.errors.pattern">User ID can contain only alpha numeric characters [a-zA-Z0-9]</div> </div> |
The attributes
required
, minlength
, maxlength
and pattern
can be added to the <input>
form control and and the form control will then be binded to data model properties in the component using directives like ngModel
.
This template-driven approach is asynchronous and the Angular will create the form control objects under the hood.
Reactive forms
Angular reactive forms supports reactive programming style and we need to manage the data flow explicitly. In reactive forms, you need to create the form control objects in the component and bind them to the form control elements in the component template.
You can push data model values into the form controls and pull user changed values back. In this approach you will use the form control objects directly in the component and hence the value updates are synchronous. To use the reactive forms, you need to import ReactiveFormsModule
in the app.module.ts
.
The revised app.module.ts
will be as below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; @NgModule({ imports: [ BrowserModule, FormsModule, ReactiveFormsModule ], declarations: [ AppComponent ], providers: [ ], bootstrap: [ AppComponent ] }) export class AppModule { } |
Reactive forms validation
Let’s see a quick example of how to implement the validation in reactive forms. Access the sample code for this example from the below link.
Also, the links are provided at the end of this post for reference.
In this example, we have a form for creating a new user. The form will take UserId
, Password
and Confirm-Password
. Initialize the form-group as below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
this.createUserFormGrp = this.fb.group({ userid: ['', [ Validators.required, Validators.minLength(5), Validators.pattern('[a-zA-Z0-9]*') ]], passwordgrp: this.fb.group({ password: ['', [ Validators.required, Validators.minLength(6), Validators.pattern(/^(?=.*[a-zA-Z])(?=.*\d)(?=.*[!@#$%^&*()_+])[A-Za-z\d][A-Za-z\d!@#$%^&*()_+]{5,19}$/) ] ], confpwd: ['', [ Validators.required ] ] }, { validator: CreateUserValidator.isPasswordMisMatched } ) }); |
If you notice the above snippet, the validation for required, minlength and pattern can be done using the Validators
present in the @angular/forms.
Implementing custom validation
The custom validation functionality can be written separately in typescript file as static
method. In this case, the method isPasswordMisMatched
will be called for validating the equality of password and confirm-password.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
import { AbstractControl, ValidationErrors } from "@angular/forms"; export class CreateUserValidator { static isPasswordMisMatched(control: AbstractControl): ValidationErrors | null { const password = control.get('password').value; const confpwd = control.get('confpwd').value; /** Mismatch when password is null or undefined */ if (password == null || password == undefined) { return { mismatch: true }; } /** Mismatch when confirm password is null or undefined */ if (confpwd == null || confpwd == undefined) { return { mismatch: true }; } /** Mismatch when either password or confirm password have empty string */ if (!((password as string).length > 0) || !((confpwd as string).length > 0)) { return { mismatch: true }; } /** Mismatch when either password and confirm password are not equal */ if (password !== confpwd) { return { mismatch: true }; } console.log('returning passwords match!...'); return null; } } |
Displaying the validation errors in component’s template
The above snippet for custom validation returns a response with attribute mismatch
. Based on the attribute value of mismatch, display the error message in the template.
1 2 3 4 5 6 7 8 9 10 11 |
<input id="confirmpwd" type="password" formControlName="confpwd" [(ngModel)]="newUser.confirmPwd" class="form-control form-control-lg input" [ngClass]="getPasswordGrp.errors?.mismatch ? 'confpwd-invalid': 'confpwd-valid'" placeholder="Confirm Password"> <div *ngIf="getConfPwd.touched && getPasswordGrp.errors?.mismatch" class="alert alert-danger"> <div *ngIf="getPasswordGrp.errors?.mismatch">Confirm password didn't match</div> </div> |
Mock back-end for rescue
Using the mock back-end for Http
requests will help to simulate the requests to server. This example application uses the mock back-end to simulate the requests to server when creating new user.
The mock back-end will return success message only when the UserId is testuser
and Password is test@123
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
import { Http, BaseRequestOptions, Response, ResponseOptions, RequestMethod } from '@angular/http'; import { MockBackend, MockConnection } from '@angular/http/testing'; export function mockBackendFactory(backend: MockBackend, options: BaseRequestOptions) { backend.connections.subscribe((connection: MockConnection) => { // setTimeout() function to simulate an asynchronous call // to the server that takes 2 second. setTimeout(() => { // // Mock implementation of /api/create-user // if (connection.request.url.endsWith('/api/create-user') && connection.request.method === RequestMethod.Post) { console.log('request received by Mock'); let body = JSON.parse(connection.request.getBody()); if (body.userId === 'testuser' && body.password === 'test@123') { connection.mockRespond(new Response( new ResponseOptions({ status: 200, body: { responseCode: 'SUC', respMessage: 'User created successfully' } }) )); } else { connection.mockRespond(new Response( new ResponseOptions({ status: 200, body: { responseCode: 'ERR', respMessage: 'User creation failed' } }) )); } } }, 2000); }); return new Http(backend, options); } export let mockBackendProvider = { provide: Http, useFactory: mockBackendFactory, deps: [MockBackend, BaseRequestOptions] }; |
API call from component
Trigger the API call from component on successful form submission. The below snippet shows the implementation of calling the API provided by mock back-end.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
/** * Method to handle the Create User Form submission */ createUser(): void{ /** Show the loading spinner since the API call will take few seconds to complete */ this.sharedService.showLoadingSpinner.next(true); this.sharedService.loadingSpinnerMessage.next('Creating User...'); /** call the user serice to create new user */ this.userService.createNewUser(this.newUser) .subscribe( result => { this.sharedService.showLoadingSpinner.next(false); // hide the loading spinner this.formSubmitted = true; if(result){ this.userCreationFailed = false; this.alertMessage = 'User created Successfully'; } else{ this.userCreationFailed = true; this.alertMessage = 'User creation failed'; } }); } |
Create User Form
Here is the reactive form for creating new user in this example application.
Displaying success message on completion of Create User API call
Conclusion
In this post, we have discussed about the different form building techniques provided by Angular. The reactive form approach is very powerful and makes building the complex forms easier. Also, we have developed a sample application which makes use of reactive forms and performs validation using custom validators.
Thank you for reading this post. What next? Start building your single page applications and make use of the reactive forms offered by Angular.
Downloads: angular-reactive-forms-example.zip
0 Comments