Cross Field Validation in Angular

Nitheesh KR
Level Up Coding
Published in
4 min readJun 5, 2020

--

Prerequisites: Basic knowledge in Angular Reactive Forms

Angular provides FormControl validation in order to determine whether a form field is valid or not. These form control validation helps to determine the validity of a form field with some predetermined rules. These rules are purely defined specifically to that form control. The defined validator function compares the value of that particular form control against a list of predetermined rules to determine the validity of that specific field.

Consider the scenario where the validity of one form field depends not just that particular form control, but on the value of another field as well. In this scenario, we cannot simply define the validator to that particular FormControl level since the validity of that form control depends on the value of another form control as well.

Determining the validity of a FormControl based on an another FormContol is what called as Cross Field Validation.

Let’s consider a scenario, we have a user registration form where the user needs to enter a password as well as a confirm password. Both the password and confirm password may have some predefined rules like min length, max length, pattern etc… Apart from these rules, there is an additional rule to be satisfied i.e, the password and the confirm password fields should be the same. Let’s see how we can do these kinds of multi-field validations in Angular.

In this scenario, we can define the validator function to the FormGroup level rather than the FormControl level. Defining the validator in the FormGroup level gives the validator function control to all the FormControl that are inside the form group.

In order to define a validator function to a FormGroup, we can pass the validator as the second argument to the FormGroup while creating the same.

So let’s have a look at how our FormGroup will be defined.

ngOnInit(): void {
this.registerForm = this.formBuilder.group({
passsword: ['',
[Validators.required,
Validators.minLength(3),
Validators.maxLength(10)]],
confirm_passsword: ['',
[Validators.required,
Validators.minLength(3),
Validators.maxLength(10)]],
}, {
validator:
CustomValidator.passwordValidator(
'passsword', 'confirm_passsword'
)
});
}

Here we have added two sets of validators. For the FormControls , we have added required, minLength and maxLength validators, and for the FormGroup , we have added a custom validator namely passwordValidator. Also we are sending the form control names as the parameter to the validator function, just to have some dynamicity.

Let’s have a look at our custom validator function as well.

export class CustomValidator {
static passwordValidator(formCtrlOne, formCtrlTwo) {
return (fg: FormGroup) => {
// Select the two form conrols from the form group
// on which the comparison is to be performed.
const fieldOne = fg.controls[formCtrlOne];
const fieldTwo = fg.controls[formCtrlTwo];
if (fieldOne && fieldTwo) {
if(fieldOne.value || fieldTwo.value) {
if (fieldOne.value !== fieldTwo.value) {
// Use set error methods like this to append
// 'password_mismatch' error
// with the existing errors.
fieldOne.setErrors({
...fieldOne.errors,
...{ 'password_mismatch': true }
});
fieldTwo.setErrors({
...fieldTwo.errors,
...{ 'password_mismatch': true }
});
} else {
let fieldOneError = {...fieldOne.errors};
delete(fieldOneError['password_mismatch']);
// If there is no keys in the error object,
// it means that the control has no error
// In that case set the object as null
// Setting null as error to a form field
// makes the form control valid
fieldOneError =
Object.keys(fieldOneError).length > 0 ?
fieldOneError : null;
fieldOne.setErrors(fieldOneError);

let fieldTwoError = {...fieldTwo.errors};
delete(fieldTwoError['password_mismatch']);
fieldTwoError =
Object.keys(fieldTwoError).length > 0 ?
fieldTwoError : null;
fieldTwo.setErrors(fieldTwoError);
}
}
}
}
}
}

In the CustomValidator function, we will receive the form control names as arguments and by using these names we can simply select the FormControl from the FormGroup by using formgroup.controls[controlname]. Here we expect both the form controls to have the same value, as a custom validation.

If the form control values do not match, we can update the form control by adding a custom error password_mismatch. We can append this custom error with the existing error object by formfield.setErrors({…formfield.errors, …{ ‘password_mismatch’: true }}), this will keep the existing error and will add the new one as well.

In order to remove the custom error once our custom validation is satisfied, we will select the current error object and deletes our custom error object from that and will set the resultant object as the error object of the form field itself. This will help in retaining all the existing error other than our custom error.

Please note, if the error object after deleting our custom error does not have any properties, the form field is valid. In this scenario we need to set the error as null manually or else setting an empty object as the error to a FormControl will make the FormControl field remains in the invalid state itself.

Now that you have read about cross validation, you might like to play around with this, a bit.

Please find the working fiddle here

https://stackblitz.com/edit/angularcrossfieldvalidation

Or you may checkout my github repository
https://github.com/devKR2911/cross-field-validation

Happy Coding. :)

--

--

Mean stack developer Angular | React | Vue JS | Node JS | Mongo DB