I have requirements like if one field has value make other fields optional/remove required. I have tried like give below example using custom validator but if not use updateValueAndValidity
form status not getting updated and it shows invalid
and if I am using updateValueAndValidity
getting an error RangeError: Maximum call stack size exceeded
.
How to solve this.
here is the custom validator code and stackblitz:
EitherMobileOrWorkPhoneRequired(
control: any
): ValidationErrors | null {
const mobile:any = control.get('mobile');
const workPhone:any = control.get('workPhone');
const isNumberEmpty = (formGroup: AbstractControl): boolean => {
const number = (<any>formGroup.get('number')).value;
return number === undefined || number === null || number === '';
};
if (isNumberEmpty(mobile) && isNumberEmpty(workPhone)) {
mobile.get('number').setValidators(Validators.required);
workPhone.get('number').setValidators(Validators.required);
return { required: true };
} else if (isNumberEmpty(mobile)) {
mobile.get('number').clearValidators();
} else if (isNumberEmpty(workPhone)) {
workPhone.get('number').clearValidators();
}
control.updateValueAndValidity();
return null;
}
CodePudding user response:
The RangeError: Maximum call stack size exceeded
error is thrown because you're calling the updateValueAndValidity()
function within the validator itself.
In Angular, it's recommended to avoid managing (remove/add) the validators from the validator function itself, and instead, you can do that outside the validator function, and keep the function only to check the validity of the control value and return ValidationErrors
or null
.
In your case, to handle the cross-controls validator on FormGroup
, you can do the following:
- Remove the
Validators.required
validator applied to each control. - Use the
*ngIf="this.rootForm.errors?.required"
to check if the form is invalid in component template, instead of checking each control error. - Change the custom validator to be like the following:
eitherMobileOrWorkPhoneRequired(control: any): ValidationErrors | null {
const mobile: any = control.value.mobile?.number;
const workPhone: any = control.value.workPhone?.number;
if (isNumberEmpty(mobile) && isNumberEmpty(workPhone)) {
return { required: true };
}
return null;
}
And here is a working version of your StackBlitz: https://stackblitz.com/edit/base-angular-12-app-jucjpk
CodePudding user response:
You have a continuous loop because you are calling updateValueAndValidity()
while the validator is running.
This method:
Recalculates the value and validation status of the control.
So when you call that, the EitherMobileOrWorkPhoneRequired
logic re-runs over and over and over because you are triggering a recalculation of the validators.
Remove the updateValueAndValidity()
call and it works as expected.
Validator update:
EitherMobileOrWorkPhoneRequired(control: any): ValidationErrors | null {
const mobile: any = control.get('mobile');
const workPhone: any = control.get('workPhone');
const isNumberEmpty = (formGroup: AbstractControl): boolean => {
const number = (<any>formGroup.get('number')).value;
return number === undefined || number === null || number === '';
};
if (isNumberEmpty(mobile) && isNumberEmpty(workPhone)) {
return { required: true };
}
return null;
}
}
Form now uses a single error message for the group, instead of one per control:
<b style="color:red;font-size:20px;"
>1.Initial both required <br />
2.When I enter mobile number , the work phone number becomes optional and vice
versa
<br />
3.Form Status should be valid on submit
</b>
<hr />
<form [formGroup]="rootForm" (ngSubmit)="onSubmit(rootForm.value)">
<div *ngIf="!this.rootForm.valid" >
<div style="color:red;" *ngIf="!this.rootForm.valid">
A mobile or work phone number must be provided.
</div>
</div>
<div formGroupName="mobile">
<label>Mobile number</label>
<input type="text" formControlName="number" />
</div>
<div formGroupName="workPhone">
<label>WorkPhone number</label>
<input type="text" formControlName="number" />
</div>
<button type="submit">submit</button>
</form>