I am working with Angular reactive forms. In my scenario, validators are added dynamically to my controls.
This is done like follows:
const myControl = myFormGroup.get('myControl');
if (myControl.validator) {
myControl.setValidators([ myControl.validator, Validators.required ]); //preserve existing validators
} else {
myControl.setValidators([ Validators.required ]);
}
This works just fine.
The Problem
I need a way to also remove a specific validator while preserving the others.
myControl.validators
only returns a function but not an array of validators which makes it impossible to pick one to remove while leaving the others.
I know there are workarounds like keeping track of the existing validators elswhere, etc.
My question however is: can this be achieved directly on the AbstractControl
?
The following issue on Github discusses the problem: https://github.com/angular/angular/issues/13461
The solutions provided there don't seem to work for me, as I am not only having to deal with the required validator but also with numerous custom validators.
Thanks in advance for any hints or solutions to this!
Cheers, Mike
Since there is no elegant solution to this, I have bitten the bullet to write my own RequiredIf validator.
It does two things:
Here's my code of the validator (just as I use it currently, no time to optimize it).
import { FormGroup, ValidationErrors, Validators } from '@angular/forms';
import { Subscription } from 'rxjs';
export class ValidationSubscriptionCache {
static observedPropertyCombinations = new Array<string>();
static subscriptions = new Array<Subscription>();
static unsubscribeAll() {
for (let subscription of ValidationSubscriptionCache.subscriptions) {
subscription.unsubscribe();
}
}
}
export function CombineProperties(property: string, otherProperty: string) {
return `${ property }.${ otherProperty }`;
}
export class ValidatorCache {
static cache = new Array<ValidatorsOfProperty>();
static currentlyRequiredProperties = new Array<string>();
static isCurrentlyRequired(property: string) {
return ValidatorCache.currentlyRequiredProperties.indexOf(property) > -1;
}
static removeFromCurrentlyRequiredProperties(property: string) {
const index = ValidatorCache.currentlyRequiredProperties.indexOf(property, 0);
if (index > -1) {
ValidatorCache.currentlyRequiredProperties.splice(index, 1);
}
}
static isInCache(property: string) {
const found = this.cache.find(x => x.property === property);
if (found) {
return true;
}
return false;
}
static addToCache(property: string, validators: any) {
const toAdd = new ValidatorsOfProperty();
toAdd.property = property;
toAdd.validators = validators;
ValidatorCache.cache.push(toAdd);
}
static popFromCache(property: string): ValidatorsOfProperty {
const item = ValidatorCache.cache.find(x => x.property === property);
const index = ValidatorCache.cache.indexOf(item, 0);
ValidatorCache.cache.splice(index, 1);
return item;
}
}
export class ValidatorsOfProperty {
property: string;
validators: any;
}
export function ValidateRequiredIf(property: string, otherProperty: string, valueThatMakesRequired) {
return (formGroup: FormGroup): ValidationErrors | null => {
const propertyCombination = CombineProperties(property, otherProperty);
const otherValue = formGroup.get(otherProperty).value;
//register changes of other property and refresh validation if it changes
if (ValidationSubscriptionCache.observedPropertyCombinations.indexOf(propertyCombination) < 0) {
ValidationSubscriptionCache.observedPropertyCombinations.push(propertyCombination);
const valueChangeSubscription =
formGroup.get(otherProperty).valueChanges
.subscribe(() => {
formGroup.get(property).markAsTouched();
formGroup.get(property).updateValueAndValidity();
});
ValidationSubscriptionCache.subscriptions.push(valueChangeSubscription);
}
//set or remove the required validator based on the depending property
const isRequired =
otherValue !== null &&
otherValue !== undefined &&
otherValue.toString() === valueThatMakesRequired.toString();
if (isRequired && !ValidatorCache.isCurrentlyRequired(property)) {
ValidatorCache.currentlyRequiredProperties.push(property);
ValidatorCache.addToCache(property, formGroup.get(property).validator);
if (formGroup.get(property).validator) {
formGroup.get(property).setValidators([ formGroup.get(property).validator, Validators.required ]);
} else {
formGroup.get(property).setValidators(Validators.required);
}
}
if (!isRequired && ValidatorCache.isCurrentlyRequired(property)) {
ValidatorCache.removeFromCurrentlyRequiredProperties(property);
formGroup.get(property).setValidators(ValidatorCache.popFromCache(property).validators);
}
return null;
};
}
And here's the application on the form:
this.myForm.setValidators(
[
//city is required if the country is switzerland
ValidateRequiredIf('city', 'nationality', 'switzerland'),
//[add more]
]);
additionally, you have to call ValidationSubscriptionCache.unsubscribeAll();
in the ngOnDestroy()
of your component holding the form.
Cheers, Mike