I wanted a more generic version of it so I have written an extra validator for it that can be composed together with other validators. I'm still just starting to look at the forms module so don't expect this to be the most efficient code ever or to work in edge cases, but it's a good start. It works for normal use cases and might serve as a good starting point for others.
Made for rc.4 and the new forms module, the revalidateOnChanges part may be horrible (not sure of the best way to cause this behaviour), use at your own risk! :)
How to use it
The validator takes two arguments, a conditional function that is given the formGroup and that is expected to return true if the validation is to apply and false otherwise, and a validator (which may be a composition). It will revalidate the field when the formGroup is updated, and it's can currently only check things inside the same formGroup, but that should be easy to fix.
this.formBuilder.group({
vehicleType: ['', Validators.required],
licencePlate: [
'',
ExtraValidators.conditional(
group => group.controls.vehicleType.value === 'car',
Validators.compose([
Validators.required,
Validators.minLength(6)
])
),
]
});
In this example you have two fields, vehicleType and licencePlate. The conditional statement will apply the composed validator (required and minLength) if the vehicleType is "car".
You can use compose to apply several different conditionals that may or may not apply at the same time. Here is a slightly more complex example:
this.formBuilder.group({
country: ['', Validators.required],
vehicleType: ['', Validators.required],
licencePlate: [
'',
Validators.compose([
ExtraValidators.conditional(
group => group.controls.vehicleType.value === 'car',
Validators.required
),
ExtraValidators.conditional(
group => group.controls.country.value === 'sweden',
Validators.minLength(6)
),
])
]
});
In this case we apply required if type is "car" and we apply minLength if country is "Sweden". If only one condition applies only that validation will apply, if both conditions applies then both validations apply.
The validator itself
Note that the object comparison is just a simple brute force because we are working with small object, if you are using Ramda or something you could cut a lot of code.
export class ExtraValidators {
static conditional(conditional, validator) {
return function(control) {
revalidateOnChanges(control);
if (control && control._parent) {
if (conditional(control._parent)) {
return validator(control);
}
}
};
}
}
function revalidateOnChanges(control): void {
if (control && control._parent && !control._revalidateOnChanges) {
control._revalidateOnChanges = true;
control._parent
.valueChanges
.distinctUntilChanged((a, b) => {
// These will always be plain objects coming from the form, do a simple comparison
if(a && !b || !a && b) {
return false;
} else if (a && b && Object.keys(a).length !== Object.keys(b).length) {
return false;
} else if (a && b) {
for (let i in a) {
if(a[i] !== b[i]) {
return false;
}
}
}
return true;
})
.subscribe(() => {
control.updateValueAndValidity();
});
control.updateValueAndValidity();
}
return;
}
NOTE: remember to import the operator:
import 'rxjs/add/operator/distinctUntilChanged';