Cross Field Validation in Angular
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 anotherFormContol
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. :)