Search code examples
angularionic3angular4-forms

cannot correctly get angular asynchronous validation error object


I am validating user inputs using reactive form with built-in validators and a custom validator. And i would like to provide front-end feedback/errors to users, but i cannot seems to correctly get the validation error object back. I am not getting any errors or warnings and console logs seems correct, so i don't really know what went wrong. Here're my code:

----Validators service name.valid.ts:

import { FormGroup, FormBuilder, Validators, AbstractControl } from '@angular/forms';
import { AngularFirestore } from 'angularfire2/firestore';
import { map, take } from 'rxjs/operators';

export class nameValidator {
  static validname(afs: AngularFirestore) {
    return (control: AbstractControl) => {
      const username = control.value.toLowerCase();
    console.log('username='+username)
      return afs.collection('usrs', ref => ref.where('name', '==', username) )
       .valueChanges().pipe(
          take(1),
          map(res => 
            {
              (res.length>=1) ? { 'validname': fail } : null;
            } 
          )
        )
    }
  }
}

----Component relevant excerpts:

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { AngularFirestoreCollection,AngularFirestoreDocument, AngularFirestore} from 'angularfire2/firestore';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { nameValidator } from  '../../validators/name.valid';

@IonicPage()
@Component({
  selector: 'page-register',
  templateUrl: 'register.html',
})
export class RegisterPage {

  formOne: FormGroup;

  constructor(public navCtrl: NavController, public navParams: NavParams
    , private afs: AngularFirestore
    , private fmBuilder: FormBuilder) 
    {

    this.formOne = this.fmBuilder.group({
      name      : ['TestnameA', Validators.compose([Validators.required ,Validators.pattern('[a-zA-Z]*')])
                    ,nameValidator.validname(this.afs)],
      password  : ['testaa', Validators.compose([Validators.required ,Validators.minLength(5)])],
    }); 

  }//end constructor

----Template/html relevant excerpts:

<ion-content>
        <form novalidate [formGroup]="formOne">
            <input id="name" type="text" formControlName="name" required>

            <div *ngIf="name.invalid && name.dirty" >
                {{ name.value }} is already taken
            </div>

            <div *ngIf="name.valid">
                {{ name.value }} is available
            </div>

            <div *ngIf="name.pending">
                Checking availability of {{ name.value }}
            </div>
         </form>
     </ion-content>

When i input name that is already taken in my firestore db, the name will wrongly still be flagged as valid in the front-end. But from debugging brekapoints in my nameValidator service code, I know that res.length is 1. So somehow, the validator is able to detect the validation error, but did not return the correct status.
I have also noted that my validator is asynchronous, thus it is placed as the 3rd argument formbuilderGroup and i have also tried adding delays into my checks, but make no difference.
Will appreciate any advice. thanks.


Solution

  • In Angular 5, the Async validator service should be of the following syntax for it to work :

     import { AsyncValidatorFn, AbstractControl, ValidationErrors } from '@angular/forms';
    import { AngularFirestore } from 'angularfire2/firestore';
    import { Observable } from "rxjs/Observable";
    import { map, take } from 'rxjs/operators';
    
    export class nameValidator {
          static validname(afs: AngularFirestore): AsyncValidatorFn  {
            return (control: AbstractControl): Observable<ValidationErrors> => {
              const username = control.value.toLowerCase();
            console.log('username='+username)
              return afs.collection('usrs', ref => ref.where('name', '==', username) )
               .valueChanges().pipe(
                  take(1),
                  map(res => 
                    {
                     return (res.length>=1) ? { 'validname': fail } : null;
                    } 
                  )
                )
            }
          }
        }