I have the following form that I would like to validate from a value in the ngrx store
this.form = new FormGroup({
serviceNr: new FormControl('', {
validators: [Validators.required],
}, [this.doubleServiceNrAsyncValidator()]),
...
});
I made sure my validator is finite, as per Angular docs
ngOnDestroy(): void {
this.isDestroyed.next(true);
this.isDestroyed.complete();
}
in my template
<mat-error *ngIf="form.get('serviceNr').hasError('existingNr')">
{{ 'GENERAL.ALERTS.ERROR.RESOURCE_ALREADY_EXIST' | translate : { number: this.form.get('serviceNr').value } }}
</mat-error>
and my validate function looks like this:
doubleServiceNrAsyncValidator = () => (_) => this.store.pipe(select(selectError)).pipe(
takeUntil(this.isDestroyed),
map(err => {
if (err?.errorCode === ErrorCodeEnum.RESOURCE_ALREADY_EXIST) {
return { existingNr: true };
}
if (!!err) {
return { unknown: true };
}
return null;
}),
)
The async validator is being called, and it returns { existingNr: true }
fine. But the field is not being marked as invalid
This behaviour is caused by the fact that your
this.store.pipe(select(selectError))
observable never completes, which leads the form control which is associated with the validation to be in a PENDING
state. While the formControl
is in such state no changes will be applied to it's valid
property.
The quick fix is to use either take(1)
or first()
inside your validator like so:
doubleServiceNrAsyncValidator = () => (_) => this.store.pipe(select(selectError)).pipe(
first(),
map(err => {
if (err?.errorCode === ErrorCodeEnum.RESOURCE_ALREADY_EXIST) {
return { existingNr: true };
}
if (!!err) {
return { unknown: true };
}
return null;
}),
The first
opertor will complete the source stream, which will cause the formControl to apply all necesary changes to the valid
field
In the following CodeSandbox i have created a side by side example, which you and anyone in the future might find usefull, because this is one of the cases that you usualy dont find in the docs :D