Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 7 : Async validator change detection

Tags:

I've written an async validator to check if the entered email already exists in my database.

I use the reactive form api to build my form and I've configure my async validator to only trigger on "blur". As mentionned in the angular documentation

it works well: the validation is triggered when I leave the field BUT the error message is not shown until I interact with my form.

If I trigger the changeDetection manually with a setTimeout function in my validation, it works.

Any idea why this error is not shown directly after the validation completes?

Here is my form definition:

private initPersonalInformationFormGroup() {
  this.personalInformationFormGroup = this._formBuilder.group({
    lastName: ['', Validators.required],
    firstName: ['', Validators.required],
    gender: [Gender.MALE],
    birthDate: [''],
    birthPlace: [''],
    nationality: [''],
    inss: ['', [Validators.minLength(11), Validators.maxLength(11), Validators.pattern('[0-9]{11}')]],
    email: ['', {
      validators: [Validators.required, Validators.email],
      asyncValidators: [this._studentEmailValidator()],
      updateOn: 'blur'
    }],
    phoneNumber: [null, Validators.pattern('\\+32[1-9][0-9]{7,8}')],
    address: this._formBuilder.group({
      street: ['', [Validators.maxLength(60)]],
      houseNumber: ['', [Validators.maxLength(10)]],
      city: ['', [Validators.maxLength(60)]],
      postalCode: [null, [Validators.min(1000), Validators.max(9999)]],
    }, {
      validators: this._completeAddressValidator()
    }),
    previousSchool: ['', Validators.maxLength(60)],
    additionalInformation: ['']
  })
}

And my validation method :

private _studentEmailValidator(): AsyncValidatorFn {
  return (control: FormGroup): Observable<{ [key: string]: any } | null> => {
    const email = control.value;
    // setTimeout(() => this._checkDetectorRef.detectChanges(), 5000);
    return this._adminFacade.checkStudentWithEmailExists(email).pipe(
      take(1),
      map(exists => exists ? {'emailAlreadyUserByStudent': {value: email}} : null),
      catchError(() => null)
    );
  }
};

and the part of the template :

<mat-form-field fxFlex="60">
  <mat-placeholder>
    <mat-icon>email</mat-icon>
    <span> Email</span>
  </mat-placeholder>
  <input matInput formControlName="email">
  <mat-error *ngIf="email.hasError('emailAlreadyUserByStudent')">
    Email déjà utilisé.
  </mat-error>
  <span *ngIf="email.pending">Validating...</span>
</mat-form-field>
like image 963
Stéphane Avatar asked Mar 11 '19 13:03

Stéphane


1 Answers

As a workaround (I'm still not sure it's the best way to do it because it's not necessary in the code examples..), I've added a manual detectChanges at the end of my validator function :

  private _studentEmailValidator(): AsyncValidatorFn {
return (control: FormGroup): Observable<{ [key: string]: any } | null> => {
  const email = control.value;
  return this._adminFacade.checkStudentWithEmailExists(email).pipe(
    take(1),
    map(exists => exists ? {'emailAlreadyUserByStudent': {value: email}} : null),
    catchError(() => null),
    tap(() => setTimeout(() => this._checkDetectorRef.detectChanges(), 0))
  );
}

};

like image 153
Stéphane Avatar answered Sep 19 '22 02:09

Stéphane