Angular has built-in input validators for common input validation such as checking min and max length, email etc. These built-in validators can only take you so far, at some point you might need to build a custom validator. In this post, I will show you how to build your own custom validators for input and a validator to check whether password and confirm password match before submitting. This will help you be able to build custom validators for both form group and specific form controls. So, without further ado, lets get started:
How it will work
I am going to assume that you have some prior knowledge on Reactive Forms in Angular and using built-in validators. If not, please checkout this page for a quick tutorial on Reactive Forms. We are going to have three custom validators. The first validator will check whether the current user is over the age of 18 and under the Age of 60. While the second validator will check whether the email provider is theinfogrid.com (this blogs domain name). The last validator will check to see the password and confirm password are a match.
Building a Custom Validator
We are going to create a class known as CustomValidators, which will house our validator functions. This will allow us to access all our validator functions under a single class.
ng generate class custom-validator
Next, we need to first create validators for the first two cases – AgeLimit
and EmailDomain
Validators. We are going to add the methods as static methods inside our CustomValidator
class. The names of our validator classes shall have the term Validator appended. So that, AgeLimit
becomes AgeLimitValidator
and EmailDomain
becomes EmailDomainValidator
. Each of our two methods will return a Validator Function (ValidatorFN
) after evaluating whether the control value is valid or not. In case where the value is valid, the validator function shall return a null value and an object if invalid. The object shall have a name and a true value i.e.
{ ageLimit: true } or { emailDomain: true }
AgeLimitValidator Function
The first validator – AgeLimitValidator
– shall look like this:
NB: Follow the comments inside the code for further clarifications.
static ageLimitValidator(minAge: number, maxAge: number): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } | null => {
// if control value is not null and is a number
if (control.value !== null) {
// return null if it's in between the minAge and maxAge and is A valid Number
return isNaN(control.value) || // checks if its a valid number
control.value < minAge || // checks if its below the minimum age
control.value > maxAge // checks if its above the maximum age
? { ageLimit: true } // return this incase of error
: null; // there was not error
}
return null;
};
}
It takes in the minimum and maximum age as parameters and uses them to evaluate age limit.
EmailDomainValidator Function
The second validator – EmailDomainValidator
– function shall look like this:
NB: Follow the comments inside the code for further clarifications.
static emailDomainValidator(domain: string): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } | null => {
// if control value is not undefined, then check if the domain name is valid
if (control.value !== null) {
const [_, eDomain] = control.value.split('@'); // split the email address to get the domain name
return eDomain !== domain // check if the domain name matches the one inside the email address
? { emailDomain: true } // return in case there is not match
: null; // return null if there is a match
}
return null; // no error, since there was no input
};
}
Using the Custom Validators
To use your custom validator inside your reactive forms, import the class and call the validator function as you would a built-in validator.
createMyForm(): FormGroup {
return this.fb.group({
...
age: [
null,
Validators.compose([CustomValidator.ageLimitValidator(18, 60)])
],
email: [
null,
Validators.compose([
Validators.email,
CustomValidator.emailDomainValidator('theinfogrid.com')
])
],
...
});
}
As you can see above, you can easily mix your custom validators and built-in validators. Now, inside your template, it’s a matter of checking whether that form control has the specific error and show the error message:
//For Age Limit Validator
<mat-error
*ngIf="myform.controls['age'].touched && myform.controls['age'].hasError('ageLimit')"
>
The age limit is between 18 and 60 years!
</mat-error>
//For Email Domain Validator
<mat-error
*ngIf="myform.controls['email'].touched && myform.controls['email'].hasError('emailDomain')"
>
Email address must be from theinfogrid.com
</mat-error>
And that’s it, we now have a custom validator for our form controls.
Custom Validators for Forms
The first two validators are for individual controls. What about if you wanted to validate something that involved more than one control. Like, checking whether password and confirmation passwords are a match. Here we will need a validator for our form group instead of individual form controls. Inside our CustomValidator
class, let’s add a third validator method for checking whether password match – PasswordMatch
– we will call it PasswordMatchValidator
. Then we are going to get the password and confirmPassword
values from our form and check if they match. Instead of returning a validator function like in the previous two instances, we will instead set an error in our form control for both password and confirmPassword. So, our new validator method will look like this:
NB: Follow the comments inside the code for further clarifications.
static passwordMatchValidator(control: AbstractControl) {
const password: string = control.get('password').value; // get password from our password form control
const confirmPassword: string = control.get('confirmPassword').value; // get password from our confirmPassword form control
// compare is the password math
if (password !== confirmPassword) {
// if they don't match, set an error in our confirmPassword form control
control.get('confirmPassword').setErrors({ NoPassswordMatch: true });
}
}
After that, we can add it to the list of our validators of the form group, not the control validators:
return this.fb.group(
{
// ...
password: [null, Validators.compose([Validators.required])],
confirmPassword: [null, Validators.compose([Validators.required])]
},
{
// our validator for the form group
validator: CustomValidator.passwordMatchValidator
}
);
And then you can add the error message in your confirmPassword form control as shown below:
<mat-error
*ngIf="myform.controls['confirmPassword'].touched && myform.controls['confirmPassword'].hasError('NoPassswordMatch')"
>
Your password and confirm password do not match
</mat-error>
I hope this post will enable you to create your own custom validators for your own form. You can find the full code on Github.