In this article, we’ll learn the generic form validators, including synchronous and asynchronous for both template-driven and reactive forms. Building an Angular application comprising many forms can be seamlessly loaded. We need to deal with validation messages on each component of an application. Hence, one of the ways to reduce your load is to write a generic validation class that deals with all your validation messages.
Besides, this will be in a meaningful way to restore the code on your HTML template. It will also provide you one source code of error messages with the flexibility to override the error message on each component
On the flip side, it includes writing minor code on the component and more files.
But I think the pros defeated the cons when dealing with multiple forms in unique components.
What we are building?
Angular has two types of forms: template-driven forms and reactive forms; having role-based authorization support in Angular is what makes it much stronger.
Let’s focus on reactive forms.
We will learn how to validate a simple Login form and also show Sign-up form with generic validation using a reactive form.
The form input values are only console log when you click on submit. I did this to keep focus on form validation, but you can do any type you want with the form input values.
What is Template-driven form in Angular?
Template-driven forms are forms where we write the logic of validations, different types of validations, control statements, etc., in the template part of the code. The template is reliable for setting up the form, the validation, control, group, and many more.
What is Reactive form in Angular?
Reactive forms are forms where we have to define the template of the form in the component class. we build the form model with Form Groups, Form Controls, and Form Arrays. Then, we have to attach it to the HTML form in the template. This is different from the template-driven forms, where we define the logic of validations and different controls in the HTML template.
Advantages of using Reactive Forms that we define:
- It is easy to write unit tests in reactive forms.
- It is easy to handle validation in reactive forms.
Disadvantage of using Reactive Forms that we define:
- It is required extra coding implementation in the component.
We need to build example,
Step-1: First of all, we need to run this command in command prompt.
ng new generic-reactiveforms
Figure-1: ng new generic-reactiveforms
Step-2: In which we required to two types of components here we shown the command. First, we need to create folder in the src\app\modules.
ng g c login
Figure-2: ng g c login
ng g c sign-up
Figure-2: ng g c login
Step-3: Import ReactiveFormsModule in the app. module.ts
Now, import ReactiveFormsModule into our app module and add it to the imports array.
import { NgModule } from ‘@angular/core’;
import { BrowserModule } from ‘@angular/platform-browser’;
import { AppRoutingModule } from ‘./app-routing.module’;
import { AppComponent } from ‘./app.component’;
import { LoginComponent } from ‘./modules/login/login.component’;
import { SignUpComponent } from ‘./modules/sign-up/sign-up.component’;
import { ReactiveFormsModule } from ‘@angular/forms’;
@NgModule({
declarations: [
AppComponent,
LoginComponent,
SignUpComponent
],
imports: [
BrowserModule,
AppRoutingModule,
ReactiveFormsModule ],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
src/app/app.module.ts
Step-4 Create a generic validation class and password confirmation validator.
create a shared folder inside the root app folder. Then inside the shared folder, create a generic-validator.ts file. create a password support validator to control if our password and confirm password match. Write the following code:
generic-validator.ts
password-matcher.ts
import {FormGroup} from ‘@angular/forms’;
const VALIDATION_MESSAGES = {
email: {
required: ‘Required’,
email: ‘This email is invalid’
},
password: {
required: ‘Required’,
minlength: ‘The password length must be greater than or equal to 6’
},
confirmPassword: {
required: ‘Required’,
match: ‘Password does not match’
},
firstName: {
required: ‘Required’
},
lastName: {
required: ‘Required’
}
};
export class GenericValidator {
constructor(private validationMessages: { [key: string]: { [key: string]: string } } = VALIDATION_MESSAGES) {}
processMessages(container: FormGroup):
{ [key: string]: string }
{
const messages = {};
for (const controlKey in container.controls) {
if (container.controls.hasOwnProperty(controlKey)) {
const controlProperty = container.controls[controlKey];
if (controlProperty instanceof FormGroup) {
const childMessages = this.processMessages(controlProperty);
Object.assign(messages, childMessages);
} else {
if (this.validationMessages[controlKey]) {
messages[controlKey] = ”;
if ((controlProperty.dirty || controlProperty.touched) &&
controlProperty.errors) {
Object.keys(controlProperty.errors).map(messageKey => {
if (this.validationMessages[controlKey][messageKey]) {
messages[controlKey] += this.validationMessages[controlKey]
[messageKey] + ‘ ‘;
}
});
}
}
}
}
}
return messages;
}
}
Src/app/shared/generic-validator.ts
import { AbstractControl } from ‘@angular/forms’;
export class PasswordMatcher {
static match(control: AbstractControl)
: void | null
{
const passwordControl = control.get(‘password’);
const confirmPasswordControl = control.get(‘confirmPassword’);
if (passwordControl.pristine || confirmPasswordControl.pristine) {
return null;
}
if (passwordControl.value === confirmPasswordControl.value) {
return null;
}
confirmPasswordControl.setErrors({ match: true });
}
}
Src/app/shared/password-matcher.ts
Step-5: Login form component
In which we can changes in app/modules/login, add the below code to the login component:
<h1 class=”title is-4″>Login</h1>
<p class=”description”>Welcome back!</p>
<form (ngSubmit)= “login()” [formGroup]= “loginForm” novalidate autocomplete=”false”>
<div class=”field”>
<div class=”control”>
<input [ngClass]=”{‘is-danger’: displayMessage.email}” class=”input is-medium” type=”email”
placeholder=”Email” formControlName=”email”>
<p *ngIf= “displayMessage.email” class=”help is-danger”>
{{ displayMessage.email }}
</p>
</div>
</div>
<div class=”field”>
<div class=”control”>
<input [ngClass]=”{‘is-danger’: displayMessage.password}” class=”input is-medium” type=”password”
placeholder=”Password” formControlName=”password”>
<p *ngIf= “displayMessage.password” class=”help is-danger”>
{{ displayMessage.password }}
</p>
</div>
</div>
<button type=”submit” class=”button is-block is-primary is-fullwidth is-medium”
[disabled]= “loginForm.invalid”>Login</button>
<br>
<small class=”has-text-centered”>
<em>
Don’t have an account
<a [routerLink]= “[‘signup’]” class=”primary-color”>Sign Up</a>
</em>
</small>
</form>
Src/app/login.component.html
import { Component , AfterViewInit, ElementRef , OnInit, ViewChildren }
from ‘@angular/core’;
import { FormGroup, FormBuilder, Validators, FormControlName }
from ‘@angular/forms’;
import { fromEvent, merge, Observable } from ‘rxjs’;
import { debounceTime } from ‘rxjs/operators’;
import { GenericValidator } from ‘../../shared/generic-validator’;
@Component({
selector: ‘app-login’,
templateUrl: ‘./login.component.html’,
styleUrls: [‘./login.component.css’]
})
export class LoginComponent implements OnInit, AfterViewInit {
@ViewChildren(FormControlName,
{ read: ElementRef })
formInputElements: ElementRef[];
loginForm: FormGroup;
displayMessage:
{ [key: string]: string } = {};
private validationMessages:
{ [key: string]: { [key: string]: string } };
private genericValidator: GenericValidator;
constructor(private fb: FormBuilder) {
this.validationMessages = {
email: {
required: ‘Required’,
email: ‘This email is invalid’
},
password: {
required: ‘Required’,
minlength: ‘The password length must be greater than or equal to 6’
} };
this.genericValidator = new GenericValidator(this.validationMessages);
}
ngOnInit() {
this.loginForm = this.fb.group({
email: [”, [Validators.required, Validators.email]],
password: [”, [Validators.required, Validators.minLength(8)]],
});
}
ngAfterViewInit(): void {
const controlBlurs: Observable<any>[] = this.formInputElements
.map((formControl: ElementRef) => fromEvent(formControl.nativeElement, ‘blur’));
merge(this.loginForm.valueChanges, …controlBlurs).pipe(
debounceTime(800)
).subscribe(value => {
this.displayMessage = this.genericValidator.processMessages(this.loginForm);
});
}
login() {
console.log(‘—form’, this.loginForm.value);
}
}
Src/app/login.component.ts
Step-6: Sign up form template
Now we have to show at the sign-up form template:
<h1 class=”title is-4″>Sign Up</h1>
<p class=”description”>Let’s get started!</p>
<form (ngSubmit)= “signup()” [formGroup]= “signupForm” novalidate
autocomplete=”false”>
<div class=”field”>
<div class=”control”>
<input [ngClass]=”{‘is-danger’: displayMessage.firstName}” formControlName=”firstName” class=”input is-medium” type=”text” placeholder=”First Name”>
<p *ngIf= “displayMessage.firstName” class=”help is-danger”>
{{ displayMessage.firstName }}
</p>
</div>
</div>
<div class=”field”>
<div class=”control”>
<input [ngClass]=”{‘is-danger’: displayMessage.lastName}” formControlName=”lastName” class=”input is-medium” type=”text” placeholder=”Last Name”>
<p *ngIf= “displayMessage.lastName” class=”help is-danger”>
{{ displayMessage.lastName }}
</p>
</div>
</div>
<div class=”field”>
<div class=”control”>
<input [ngClass]=”{‘is-danger’: displayMessage.email}” formControlName=”email” class=”input is-medium” type=”email” placeholder=”Email”>
<p *ngIf= “displayMessage.email” class=”help is-danger”>
{{ displayMessage.email }}
</p>
</div>
</div>
<div class=”field”>
<div class=”control”>
<input [ngClass]=”{‘is-danger’: displayMessage.password || displayMessage.confirmPassword }” formControlName=”password” class=”input is-medium” type=”password” placeholder=”Password”>
<p *ngIf= “displayMessage.password” class=”help is-danger”>
{{ displayMessage.password }}
</p>
</div>
</div>
<div class=”field”>
<div class=”control”>
<input [ngClass]=”{‘is-danger’: displayMessage.confirmPassword}” formControlName=”confirmPassword” class=”input is-medium” type=”password” placeholder=”Confirm Password”>
<p *ngIf= “displayMessage.confirmPassword” class=”help is-danger”>
{{ displayMessage.confirmPassword }}
</p>
</div>
</div>
<br>
<button type=”submit” class=”button is-block is-primary is-fullwidth is-medium” [disabled]= “signupForm.invalid”>Submit</button>
<br>
<small class=”has-text-centered”>
<em>
Already have an account
<a [routerLink]=”[”]” class=”primary-color”>Login</a>
</em>
</small>
</form>
Src/app/sign-up.component.html
import { ViewChildren, AfterViewInit , OnInit, ElementRef , Component}
from ‘@angular/core’;
import { FormGroup, FormBuilder, Validators, FormControlName, AbstractControl } from ‘@angular/forms’;
import { merge ,fromEvent, Observable } from ‘rxjs’;
import { debounceTime } from ‘rxjs/operators’;
import { GenericValidator } from ‘../../shared/generic-validator’;
import { PasswordMatcher } from ‘../../shared/password-matcher’;
@Component({
selector: ‘app-sign-up’,
templateUrl: ‘./sign-up.component.html’,
styleUrls: [‘./sign-up.component.css’]
})
export class SignUpComponent implements OnInit, AfterViewInit {
@ViewChildren(FormControlName,
{ read: ElementRef })
formInputElements:
ElementRef[];
signupForm: FormGroup;
displayMessage:
{ [key: string]: string }
= {};
private genericValidator: GenericValidator;
constructor(private fb: FormBuilder) {
this.genericValidator = new GenericValidator();
}
ngOnInit() {
this.signupForm = this.fb.group({
firstName: [”, [Validators.required]],
lastName: [”, [Validators.required]],
email: [”, [Validators.required, Validators.email]],
password: [”, [Validators.required, Validators.minLength(8)]],
confirmPassword: [”, Validators.required]
}, { validator: PasswordMatcher.match });
}
ngAfterViewInit(): void {
const controlBlurs: Observable<any>[] = this.formInputElements
.map((formControl: ElementRef) => fromEvent(formControl.nativeElement, ‘blur’));
merge(this.signupForm.valueChanges, …controlBlurs).pipe(
debounceTime(800)
).subscribe(value => {
this.displayMessage = this.genericValidator.processMessages(this.signupForm);
});
}
signup() {
console.log(‘—form’, this.signupForm.value);
}
}
Src/app/sign-up.component.ts
Step-7: In which we write some code: in this file app-routing. module.ts
import { NgModule } from ‘@angular/core’;
import { RouterModule, Routes } from ‘@angular/router’;
import { LoginComponent } from ‘./modules/login/login.component’;
import { SignUpComponent } from ‘./modules/sign-up/sign-up.component’;
const routes: Routes = [{
path: ”,
component: LoginComponent
},
{
path: ‘signup’,
component: SignUpComponent
}];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
src/app/app-routing.module.ts
Step-8: Now we can write in the app.component.html and <router-outlet></router-outlet>tag.
<section class=”section”>
<main class=”container”>
<div class=”columns is-multiline is-centered contain”>
<div class=”column left”>
<h1 class=”title is-1 u-margin-bottom”>Ifour Technolab Pvt Ltd.</h1>
<h2 class=”subtitle colored is-4 primary-color”>Best Company to Start Your Carrier!</h2>
</div>
<div class=”column right”>
<router-outlet></router-outlet>
</div>
</div>
</main>
</section>
Src/app/app.component.html
Step-9: Just go to tsconfig. json and set “strict”: false, otherwise you need to initialize all your variables which is a little bit annoying.
{
“compileOnSave”: false,
“compilerOptions”: {
“baseUrl”: “./”,
“outDir”: “./dist/out-tsc”,
“forceConsistentCasingInFileNames”: true,
“strict”: false,
“noImplicitReturns”: true,
“noFallthroughCasesInSwitch”: true,
“sourceMap”: true,
“declaration”: false,
“downlevelIteration”: true,
“experimentalDecorators”: true,
“moduleResolution”: “node”,
“importHelpers”: true,
“target”: “es2017”,
“module”: “es2020”,
“lib”: [
“es2018”,
“dom”
]
},
“angularCompilerOptions”: {
“enableI18nLegacyMessageIdFormat”: false,
“strictInjectionParameters”: true,
“strictInputAccessModifiers”: true,
“strictTemplates”: true
}
}
src/app/tsconfig.json
Step-10: Now we show the output: this is for Sign-Up page.
Figure-3: Sign-up page
Step-11: And here we see the output for login page.
Figure-4: Login page
Step-12: After we open Console and see the value of object.
Figure-5: Open Console and Show the Output
Conclusion
Creating a generic validator makes it simple to manage multiple form validations without using a ton of unnecessary code in your Angular application.
Author Bio: Vinod Satapara – Technical Director, iFour Technolab Pvt. Ltd.
Technocrat and entrepreneur of a reputed Dot Net Development Company with years of experience in building large scale enterprise web, cloud and mobile applications using latest technologies like ASP.NET, CORE, .NET MVC, Angular and Blockchain. Keen interest in addressing business problems using latest technologies and help organization to achieve goals.