I'm trying to force an update on the validators of all the form groups of a custom stepper just before the registration at the end of the final step, like so :
public async register(): Promise<void> {
// do stuff
// update validity of all forms from all steps
const stepForms = this.stepFactories.map(step => step.form);
stepForms.forEach(group => {
if (group !== null && group !== undefined) {
this.updateValueAndValidity(group);
}
});
// check if any form is KO
if (stepForms.every(form => form.valid || form.disabled)) {
try {
result = await this.registrationService.register(model);
} catch (error) {
// do stuff
}
// do stuff
}
}
One of the form groups contain another formGroup with an asynchronous validator with 2 calls to the server which works as follow:
export function fooAsyncValidator(fooService: FooService, fooParams: FooParams): AsyncValidatorFn {
return (group: AbstractControl): Observable<ValidationErrors | null> => {
const fooControlVal1 = group.get('FooVal1').value;
const fooControlVal2 = group.get('FooVal2').value;
// do stuff
let availableFoo: number;
// first call to server, which is answered according to chrome debugger
return fooService.getAvailableFoo(fooParams).pipe(
flatMap((avFoo: number) => {
availableFoo = avFoo;
// second call to server which is answered after I get to check the validity
return fooService.getConsumedFoo(fooParam, availableFoo);
}),
map((consumedFoo: number) => {
// do stuff with available Foo and consumedFoo
if (/* stuff */) {
return { availableFooError: true };
}
return null;
}),
);
}
}
According to chrome debugger, when I apply the updateValueAndValidity
on this form group, the first call, fooService.getAvailableFoo(...)
, is answered on time, but the second call, fooService.getConsumedFoo(...)
, is answered later. This result in the status of the form group being "PENDING".
I have found a similar subject connected to this tutorial but I don't really understand how to apply it to my current problem as I am quite new to Angular.
I found out in another part of the code that use a CdkStepper that setting the value of selectedIndex to the number of a step with the same form group would correctly update the validity of the form.
For reference, here is the code of the updateValueAndValidity
custom method, which apply updateValueAndValidity
on each fromGroup and formControl recursively:
private updateValueAndValidity(group: FormGroup): void {
group.updateValueAndValidity({ emitEvent: false });
for (const field in group.controls) { // 'field' is a string
const control = group.get(field);
if (control) {
if (control instanceof FormGroup) {
updateValueAndValidity(control);
} else {
if (control.disabled) {
control.enable({ emitEvent: false });
control.updateValueAndValidity({ emitEvent: false });
if (!control['invalidActived'] || !control.invalid) {
control.disable({ emitEvent: false });
}
} else {
control.updateValueAndValidity({ emitEvent: false });
}
}
}
}
In summary: When I update the value and validity of a form group with 2 calls to the server, the status switch to pending as one the 2 isn't answered yet. I want to know how to wait for the pending to end.
Edit: Reworked a bit of the question layout and changed title since main stepper is a custom one, not a CdkStepper.
Edit 2: So I found this github thread but it doesn't seem to get a definitive answer before being closed...
Edit 3: Found another lead in this stackoverflow post:
- this answer seems to be interesting, but I would need to include all four formGroups in the subscribe => not sure how to do it yet
- the last answer hint at this github request wherein the author describe his custom solution
I just found this code in the app I'm working on, burried inside a component:
export function awaitAsyncValidator(form: FormGroup): Observable<number> {
return timer(0, 500).pipe(
tap((timerCount: number) => {
if (timerCount > 200) {
throw new Error('The form is blocked in "pending" status.');
}
}),
filter(() => !form.pending),
take(1),
);
}
It is used with a Subscription objet attached to it in a subscribe. Currently testing it out.