Search code examples
angularangular-materialangular-validation

AsyncValidation with ngrx


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


Solution

  • 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