typescript - Angular: al agregar o eliminar validadores y actualizar el estado del formulario usando CustomValidator... obteniendo RangeError: tamaño máximo de pila excedido
Tengo requisitos como que si un campo tiene valor, hacer que otros campos sean opcionales/eliminar obligatorio. He intentado un ejemplo como el siguiente usando un validador personalizado, pero si no uso updateValueAndValidity el estado del formulario no se actualiza y muestra invalido, y si estoy usando updateValueAndValidity obtengo un error RangeError: Se superó el tamaño máximo de la pila de llamadas.
Cómo resolver esto.
aquí está el código del validador personalizado y 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;
}
Respuesta #1
El error RangeError: Maximum call stack size exceeded se produce porque estás llamando a la función updateValueAndValidity() dentro del propio validador.
En Angular, se recomienda evitar gestionar (eliminar/agregar) los validadores desde la propia función validadora y, en su lugar, puedes hacer eso fuera de la función validadora, y mantener la función únicamente para verificar la validez del valor del control y devolver ValidationErrors o null.
En su caso, para manejar el validador de controles cruzados en FormGroup, puede hacer lo siguiente:
- Elimine el validador
Validators.requiredaplicado a cada control. - Utiliza
*ngIf="this.rootForm.errors?.required"para comprobar si el formulario es inválido en la plantilla del componente, en lugar de verificar el error de cada control. - Cambia el validador personalizado para que sea como el siguiente:
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;
}
Y aquí hay una versión funcional de tu StackBlitz: https://stackblitz.com/edit/base-angular-12-app-jucjpk
Respuesta #2
Tienes un bucle continuo porque estás llamando a updateValueAndValidity() mientras el validador se está ejecutando.
Este método:
Recalcula el valor y el estado de validación del control.
Entonces, cuando llamas a eso, la lógica EitherMobileOrWorkPhoneRequired se vuelve a ejecutar una y otra y otra vez porque estás desencadenando un recálculo de los validadores.
Elimina la llamada a updateValueAndValidity() y funcionará como se espera.
Actualización del validador:
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;
}
}
El formulario ahora usa un único mensaje de error para el grupo, en lugar de uno por cada 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" class="alert">
<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>
