Reputation: 973
I want to apply conditional validation on some properties based on some other form values. I have referred some answers Angular2: Conditional required validation, but those are not fulfil my need. Because I have to implement conditional validation in 40+ form(around 30 fields) of my large enterprise application. I don't want write the same code in every component and change the FormControl name. I don't know how this can be achieved via Directive.
if age control valuev is greater than 18 than the license number field is required.
Here's my code:
this.userCustomForm = this.angularFormBuilder.group({
age:['',Validators.required],
licenseNo:[''] // Here I want to apply conditional required validation.
});
In my application there are some cases where I want set conditional validation based on nested FormGroup
or FormArray
values.
Please guide me, how can I achieve this.
Upvotes: 74
Views: 116383
Reputation: 1518
You can try the following way as well
this.userCustomForm = this.angularFormBuilder.group({
age:['',Validators.required],
licenseNo:['', condition ? Validators.required:Validators.nullValidator ]
});
You can achive the same by following
this.userCustomForm = this.angularFormBuilder.group({
age: ['', Validators.required],
licenseNo: ['']
});
handleValidations(condition: any){
if (condition) {
this.pushNotificationForm.get('licenseNo').setValidators([Validators.required]);
} else {
this.pushNotificationForm.get('licenseNo').setValidators([Validators.nullValidator]);;
}
this.pushNotificationForm.get('licenseNo').updateValueAndValidity();
}
Upvotes: 7
Reputation: 6923
To make this work nicely it is best if the solution is dynamic and robust, so it can easily and safely be used in any situation.
We can achieve this by creating a validator function on FormGroup
level that, instead of validating, adds and removes the validator(s) on the child control based on a condition function.
It is used as follows:
this.userCustomForm = this.angularFormBuilder.group({
age:['',Validators.required],
licenseNo:['']
}, {
validators: [
conditionalValidators({
licenseNo: {
condition: (formValue) => formValue.age > 18,
validators: [Validators.required]
}
})
]
});
The implementation is quite concise and straightforward:
export function conditionalValidators(groupValidators: FormGroupConditionalValidators): ValidatorFn {
return (formGroup: FormGroup) => {
for (const controlName in groupValidators) {
const control = formGroup.controls[controlName];
const { condition, validators } = groupValidators[controlName];
updateValidators(control, condition, validators, formGroup.value);
}
return null;
};
}
function updateValidators(
control: AbstractControl, condition: (formGroupValue: any) => boolean,
validators: ValidatorFn[], formGroupValue: any) {
if (condition(formGroupValue)) {
addValidators(control, validators);
} else {
removeValidators(control, validators);
}
}
function addValidators(control: AbstractControl, validators: ValidatorFn[]) {
if (validators.some((validator) => !control.hasValidator(validator))) {
control.addValidators(validators);
control.updateValueAndValidity({ onlySelf: true });
}
}
function removeValidators(control: AbstractControl, validators: ValidatorFn[]) {
if (validators.some((validator) => control.hasValidator(validator))) {
control.removeValidators(validators);
control.updateValueAndValidity({ onlySelf: true });
}
}
export interface FormGroupConditionalValidators {
[controlName: string]: {
condition: (formGroupValue: any) => boolean;
validators: ValidatorFn[];
};
}
Upvotes: 0
Reputation: 4453
We use this form-level validator to add or remove the Validators.required
validator on an input based on a condition:
export const maybeRequiredValidator = (
inputName: string,
requiredWhen: (form: AbstractControl) => boolean
): ValidatorFn =>
(form: AbstractControl): ValidationErrors | null => {
let targetInput = form.get(inputName);
if (targetInput) {
let isRequired = requiredWhen(form);
if (isRequired != targetInput.hasValidator(Validators.required)) {
if (isRequired) {
targetInput.addValidators(Validators.required);
}
else {
targetInput.removeValidators(Validators.required);
}
targetInput.updateValueAndValidity({ onlySelf: true });
}
}
return null;
};
This lets you set the required status of an input based on any number of other form fields.
Usage example based on the OP:
this.userCustomForm = this.angularFormBuilder.group({
age:['',Validators.required],
licenseNo:['']
}, {
validators: [
maybeRequiredValidator('licenseNo', form => parseInt(form.get('age').value) > 18)
]
});
EDIT
I ended up needing a more generic version of this, one that could handle multiple validators and validators other than required
. So I created this:
/**
* @description
* Form-level validator that adds or removes validators on a specified input by
* evaluating a condition for each validator.
* @usageNotes
* This must be registered as a form-level validator.
* ### Example
* ```typescript
* let form = new FormGroup({}, {
* validators: conditionalValidators(
* 'companyName',
* [
* {
* isSelected: form => form.get('addressType').value == 'business',
* validators: Validators.required // or [Validators.required, ...]
* },
* // additional validators may be added here
* ]
* )
* });
* ```
* @param inputName Name of the input that will have validators added or removed.
* @param validatorSelectors Array of objects that include a function and validator(s).
* The function receives the form and returns a Boolean value that indicates
* if the validator(s) should be applied to the input.
* @returns Validator function.
*/
export const conditionalValidators = (
inputName: string,
validatorSelectors: ReadonlyArray<ValidatorSelector>
): ValidatorFn =>
(form: AbstractControl): ValidationErrors | null => {
let targetInput = form.get(inputName);
if (targetInput) {
let anyChanges = false;
for (let selector of validatorSelectors) {
let isSelected = selector.isSelected(form);
let validators = selector.validators instanceof Array
? selector.validators : [selector.validators];
for (let validator of validators) {
if (isSelected != targetInput.hasValidator(validator)) {
anyChanges = true;
if (isSelected) {
targetInput.addValidators(validator);
}
else {
targetInput.removeValidators(validator);
}
}
}
}
if (anyChanges) {
targetInput.updateValueAndValidity({ onlySelf: true });
}
}
return null;
};
/**
* Function that returns a Boolean indicating if validator(s) should be
* applied to a form control.
* @param form The form that contains the target form control.
* @returns True if the validator(s) should be applied, else false.
*/
export interface ValidatorSelectionFn {
(form: AbstractControl): boolean;
};
/**
* Type used to conditionally select validator(s) for a form control.
*/
export interface ValidatorSelector {
/**
* Function that returns a Boolean indicating if the validator(s) should be
* applied to the form control.
*/
readonly isSelected: ValidatorSelectionFn;
/**
* Single validator or array of validators applied to the form control when
* isSelected returns true.
*/
readonly validators: ValidatorFn | ReadonlyArray<ValidatorFn>;
};
Upvotes: 2
Reputation: 175
This extends on the most upvoted answer: Since Angular 12 you can use addValidators and removeValidators. This is an advantage over setValidators and clearValidators because it does not affect any other validators on the control. So it comes down to:
this.userCustomForm.get('age').valueChanges.subscribe(val => {
if (condition) {
this.userCustomForm.controls['licenseNo'].addValidators(Validators.required);
} else {
this.userCustomForm.controls['licenseNo'].removeValidators(Validators.required);
}
this.userCustomForm.controls['licenseNo'].updateValueAndValidity();
});
Upvotes: 3
Reputation: 1331
For me it worked perfectly like this:
this.userCustomForm.get('age').valueChanges.subscribe(val => {
if (condition) {
this.userCustomForm.controls['licenseNo'].setValidators([Validators.required]);
} else {
this.userCustomForm.controls['licenseNo'].clearValidators();
}
this.userCustomForm.controls['licenseNo'].updateValueAndValidity();
});
You have to updateValueAndValidity of the form for the changes to take effect.
Upvotes: 133
Reputation: 11
you can use the following approach:
In template add :
<input formcontrol="age" (input)="changeEvent()">
In ts add:
changeEvent(){
if (this.userCustomForm.get('age').value>12) {
this.userCustomForm.controls['licenseNo'].setValidators([Validators.required]);
} else {
this.userCustomForm.controls['licenseNo'].clearValidators();
}
this.userCustomForm.controls['licenseNo'].updateValueAndValidity();
}
Upvotes: 1
Reputation: 181
There is a more generic approch which can be use for multiple purpose, not just this one.
Each time you want to conditionally add the Validators.required to a control you can use this function.
First create this function (in a service should be the best idea because it's generic, so you can use it later with different conditions in a different component, but for the example it's in the same component)
import { FormGroup, Validators } from '@angular/forms';
conditionallyRequiredValidator(masterControlLabel: string, operator: string, conditionalValue: any, slaveControlLabel: string) {
return (group: FormGroup): {[key: string]: any} => {
const masterControl = group.controls[masterControlLabel];
const slaveControl = group.controls[slaveControlLabel];
if (Function(`"use strict"; return '${masterControl.value}' ${operator} '${conditionalValue}'`)()) {
return Validators.required(slaveControl)
}
slaveControl.setErrors(null);
return null;
}
}
masterControlLabel: the control which will conditionally add the Validators.required to the slaveControl
operator: the operator you want to use to compare the masterControl value with the conditionalValue
conditionalValue: what ever the value you want the masterControl value must match to conditionally add the validator to the slaveControl
slaveControlLabel: the control which will receive (or not) the conditonal Validators.required
Second and finally, add the validator parameter (or validators parameter if mutliple validations need to be done) in your formGroup like this :
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
constructor(
private angularFormBuilder: FormBuilder,
){ }
myFormGroup: FormGroup = this.angularFormBuilder.group({
age: ['', Validators.required],
licenceNo: [''],
}, {validator: conditionallyRequiredValidator('age', '>=', 18, 'licenceNo')});
In this example if the age is strictly over or egal to 18 then the licenceNo control will be conditionally required
Upvotes: 9
Reputation: 3593
Conditionally set validator via setValidators method of FormControl class, ie
this.userCustomForm = this.angularFormBuilder.group({
age:['', Validators.required],
licenseNo:['']
});
if (condition) {
this.userCustomForm.get('licenseNo').setValidators([
Validators.required
]);
}
Upvotes: 3
Reputation: 2810
I solved this problem by doing this :
this.userCustomForm = new FormGroup({
age: new FormControl('',Validators.required)
licenseNo: new FormControl('', condition ? Validators.required : [])
});
There are another way to do this by using setValidators
and clearValidators
methods please see the following example:
if(condition) {
this.userCustomForm.get('licenseNo').setValidators(Validators.required);
} else {
this.userCustomForm.get('licenseNo').clearValidators();
}
Upvotes: 9
Reputation: 3858
My suggestion would be to use dynamic validations.
Subscribe to the changes in the age field of the userCustomForm and whenever the age reaches the condition where license needs to validated, add validators.required dynamically using setValidators()
and clear the validators dynamically using clearValidators()
whenever necessary.
this.userCustomForm.get('age').valueChanges.subscribe(val => {
if (condition) { // for setting validations
this.userCustomForm.get('licenseNo').setValidators(Validators.required);
}
if (condition) { // for clearing validations
this.userCustomForm.get('licenseNo').clearValidators();
}
this.userCustomForm.get('licenseNo').updateValueAndValidity();
});
Upvotes: 25