Using the final release or the new Angular, I wrote a reuse method to add conditional mandatory or other confirmation - to a specific 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 as follows:
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 will result in the need for any of 'city' , 'lat' or 'lng' .
In addition, if you want 'city' or 'lat' and 'lng' to be needed, you can include an additional validator, for example:
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 create a set of form controls <<29> based on the value of conditionalControlKey , i.e. if conditionalControlKey matters, all other controls in the controlKeys Array are not required, otherwise all are required.
I hope this is not too difficult for everyone to follow - I am sure that these pieces of code can be improved, but I feel that they definitely demonstrate one way to do this.