Thank you for reading my blog posts, I am no longer publishing new content on this platform. You can find my latest content on either mainawycliffe.dev or All Things Typescript Newsletter (✉️)
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:
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.
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 }
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.
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
};
}
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.
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.
In an earlier post, I wrote about Angular CLI Workspaces, a new feature in Angular 6 that allows you to have more than one project in a …
Read MoreOne of the least talked about features of Angular 6 is Angular CLI Workspaces. Workspaces or Angular CLI Workspaces give angular developers …
Read MoreIf you are looking to improve your web app both visually and functionally, icons are a very good place to start. Icons if applied correctly, …
Read MoreIn my earlier post, I covered about lazy loading of angular modules. In this post, I will cover lazy loading of scripts and styles, both …
Read MoreIt’s common for developers to have multiple application versions that target different environments i.e. development and production. It’s …
Read MoreSince the release of VSCode a few years ago, it has become very popular among developers. VSCode is my favorite text editor and use it every …
Read More