Using the final release or new of Angular, I have written a reusable method to add a Conditional Required- or other Validation -to a given set of Controls.
export class CustomValidators {
static controlsHaveValueCheck(controlKeys: Array<string>, formGroup: FormGroup): Array<boolean> {
return controlKeys.map((item) => {
// reset any errors already set (ON ALL GIVEN KEYS).
formGroup.controls[item].setErrors(null);
// Checks for empty string and empty array.
let hasValue = (formGroup.controls[item].value instanceof Array) ? formGroup.controls[item].value.length > 0 :
!(formGroup.controls[item].value === "");
return (hasValue) ? false : true;
});
}
static conditionalAnyRequired(controlKeys: Array<string>): ValidatorFn {
return (control: FormControl): {[key: string]: any} => {
let formGroup = control.root;
if (formGroup instanceof FormGroup) {
// Only check if all FormControls are siblings(& present on the nearest FormGroup)
if (controlKeys.every((item) => {
return formGroup.contains(item);
})) {
let result = CustomValidators.controlsHaveValueCheck(controlKeys, formGroup);
// If any item is valid return null, if all are invalid return required error.
return (result.some((item) => {
return item === false;
})) ? null : {required: true};
}
}
return null;
}
}
}
This can be used in your code like this:
this.form = new FormGroup({
'cityName': new FormControl('',
CustomValidators.conditionalAnyRequired(['cityName', 'lat', 'lng'])),
'lat': new FormControl('',
Validators.compose([Validators.minLength(1),
CustomValidators.conditionalAnyRequired(['cityName', 'lat', 'lng']))),
'lng': new FormControl('',
Validators.compose([Validators.minLength(1),
CustomValidators.conditionalAnyRequired(['cityName', 'lat', 'lng'])))
})
This would make any of 'city'
, 'lat'
or 'lng'
required.
Additionally, if you wanted either 'city'
or 'lat'
and 'lng'
to be required you can include an additional validator such as this:
static conditionalOnRequired(conditionalControlKey: string, controlKeys: Array<string>): ValidatorFn {
return (control: FormControl): {[key: string]: any} => {
let formGroup = control.root;
if (formGroup instanceof FormGroup) {
if (controlKeys.every((item) => {
return formGroup.contains(item);
}) && formGroup.contains(conditionalControlKey)) {
let firstControlHasValue = (formGroup.controls[conditionalControlKey].value instanceof Array) ? formGroup.controls[conditionalControlKey].value.length > 0 :
!(formGroup.controls[conditionalControlKey].value === ""),
result = CustomValidators.controlsHaveValueCheck(controlKeys, formGroup);
formGroup.controls[conditionalControlKey].setErrors(null); // Also reset the conditional Control...
if (firstControlHasValue && formGroup.controls[conditionalControlKey].value !== false) {// also checks for false (for unchecked checkbox value)...
return (result.every((invalid) => {
return invalid === false;
})) ? null : {required: true};
}
}
}
return null;
}
}
This method will make a set of form controls 'required
' based on the value of the conditionalControlKey
, i.e. if conditionalControlKey
has a value all other controls in controlKeys
Array are not required, otherwise the all are required.
I hope this isn't too convoluted for anyone to follow- I am sure these code snippets can be improved, but I feel they aptly demonstrate one way of going about this.