Angular-Custom Form Validation

Jamie Gross
ITNEXT
Published in
3 min readOct 2, 2019

--

One of the features that has always stood out for me in Angular, compared to other frameworks such as React and Vue.js, has been its built-in forms. I personally prefer reactive forms over template driven forms so this tutorial will be focused on implementing validators the reactive way.

Creating a Reactive Form

I won’t spend too much time explaining how to create the forms because this tutorial is more focused on the validators themselves, but I will give a quick overview for those who aren’t yet familiar.

form: FormGroup;
...
this.form = new FormGroup({
password: new FormControl('', [Validators.required]),
confirmPassword: new FormControl('', [Validators.required]),
phoneNumber: new FormControl('', [Validators.required, Validators.pattern(this.PHONE_NUMBER_REGEX)]),
startDate: new FormControl(null, [Validators.required]),
endDate: new FormControl(null, [Validators.required]),
}, {
validators: [
this.customValidatorsService.passwordsMatch('password', 'confirmPassword'),
this.customValidatorsService.startBeforeEnd('startDate', 'endDate', 'endBeforeStart'),
]
});

A form group is a collection of form controls. It essentially gathers all the validation and form states and combines them into a single entity. If a form control is invalid, for example, the entire form group is also invalid. The same applies for pristine/dirty, touched/untouched, etc.. You can find a more in depth explanation of form groups here.

Basic Validators

There are a certain number of validators that are provided from Validators such as required , email , and min(#). They can be added to a form control inside the second parameter (of type array) and you can add as many as you want. If a validator’s check fails the errors object on the form control and group will get a new key/value (value=true) associated to that error. The default basic validators have default keys such as 'required'.

Custom Validators

Custom validators can be quite tricky, but are extremely useful. An example of a fairly common requirement is password matching.

passwordsMatch(passwordKey: string, confirmPasswordKey: string): ValidatorFn {
return (control: AbstractControl): { [key: string]: boolean } | null => {
if (!control) { return null; }
const password = control.get(passwordKey);
const confirmPassword = control.get(confirmPasswordKey);
if (!password.value || !confirmPassword.value) {
return null;
}

if (password.value !== confirmPassword.value) {
return { passwordMismatch: true };
}
return null;
};
}

The first few lines are simple null checks, making sure the control (a.k.a the form group) and the controls (specified by keys) exist. Check that each form control has a value. If the user hasn’t typed in both fields yet, we don’t want to validate.

if (!control) { return null; }
const password = control.get(passwordKey);
const confirmPassword = control.get(confirmPasswordKey);
if (!password.value || !confirmPassword.value) {
return null;
}

Returning null from the function is equivalent to saying there is not an error. Then check if the two form control values are equal.

if (password.value !== confirmPassword.value) {
return { passwordMismatch: true };
}
return null;

Finally, to simply use the validator you do as follows:

this.form = new FormGroup({
...
}, {
validators: [
this.customValidatorsService.passwordsMatch('password', 'c confirmPassword'),
...
]
});

… where 'password' and 'confirmPassword' are the names of the form controls we want to match.

You can see another example of date checking as well in the same service file (custom-validators.service.ts), but I won’t go into detail on that. I was feeling good when I wrote this code so there are unit tests for both validator functions which you can run with jest or npm run test .

Summary

Custom form validation is a little tedious to get used to, but provides unlimited support for any form validation you are looking to accomplish. Always remember to export your custom validators from a service so you can reuse the validators anywhere in your application.

Check out my example on Github: https://github.com/orange-bees/angular-concepts-tutorials.

About Me

I work for Orange Bees, a software engineering consulting company in Greenville, SC, as a Principal Engineer. I write Angular and .NET applications, architect projects in Azure (Azure Developer Associate certified), and dabble in ElasticSearch and Node.js.
You can find me on LinkedIn.

--

--

I work for Orange Bees in Greenville, SC as a Principal Engineer. I have a BSCS and MS in Computer Science from the University of South Carolina.