Search code examples
javascriptangularfirebasegoogle-cloud-firestoreangularfire2

Angular custom validation with async service


I am using Angular with a Firestore database (with Angularfire2). To check wheter a username already exists, I made a custom validation class with a validate function. Unfortunately, no matter if the validate function returns null or { emailTaken: true }, the form seems to stay invalid. If I rewrite the function to only return null it works, so I suppose the error is located in this function - maybe it has something to do with it being async?

NameValidator class:

import { FirebaseService } from './../services/firebase.service';
import { FormControl } from "@angular/forms";
import { Injectable } from '@angular/core';

@Injectable()
export class NameValidator {
  constructor(private firebaseService: FirebaseService) { }

  validate(control: FormControl): any {
    return this.firebaseService.queryByUniqueNameOnce(control.value).subscribe(res => {
        return res.length == 0 ? null : { emailTaken: true };
    });
  }
}

The firebaseService query function:

queryByUniqueNameOnce(uniqueName: string) {
 return this.firestore.collection("users", ref => ref.where('uniqueName', '==', uniqueName))
 .valueChanges().pipe(take(1));
}

The formGroup:

 this.firstForm = this.fb.group({
  'uniqueName': ['', Validators.compose([Validators.required, this.nameValidator.validate.bind(this.nameValidator)])],
});

Solution

  • Async validators are to be the third parameters of the form control.

    Also, compose is rendered useless in the newest versions.

    'uniqueName': ['', [/*sync validators*/], [this.nameValidator.validate]]
    

    You also will have to change your validation function :

      validate(control: FormControl): any {
        return this.firebaseService.queryByUniqueNameOnce(control.value).pipe(
          map(res => !res.length ? null : ({ emailTaken: true }) )
        );
      }