17
votes

I have a problem with defining asynchrous validator in template driven form.

Currently i have this input:

<input type="text" ngControl="email"  [(ngModel)]="model.applicant.contact.email" #email="ngForm" required asyncEmailValidator>

with validator selector asyncEmailValidator which is pointing to this class:

import {provide} from "angular2/core";
import {Directive} from "angular2/core";
import {NG_VALIDATORS} from "angular2/common";
import {Validator} from "angular2/common";
import {Control} from "angular2/common";
import {AccountService} from "../services/account.service";

@Directive({
selector: '[asyncEmailValidator]',
providers: [provide(NG_VALIDATORS, {useExisting: EmailValidator, multi: true}), AccountService]
})

export class EmailValidator implements Validator {
//https://angular.io/docs/ts/latest/api/common/Validator-interface.html


constructor(private accountService:AccountService) {
}

validate(c:Control):{[key: string]: any} {
    let EMAIL_REGEXP = /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+)*\.(aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|mobi|[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i;

    if (!EMAIL_REGEXP.test(c.value)) {
        return {validateEmail: {valid: false}};
    }

    return null;

    /*return new Promise(resolve =>
        this.accountService.getUserNames(c.value).subscribe(res => {
            if (res == true) {
                resolve(null);
            }
            else {
                resolve({validateEmailTaken: {valid: false}});
            }
        }));*/
}

}

Email regex part is working as expected and form is being validated successfuly if regex is matching. But after that I want to check if e-mail is not already in use, so im creating promise for my accountService. But this doesn't work at all and form is in failed state all the time.

I've read about model driven forms and using FormBuilder as below:

constructor(builder: FormBuilder) {
this.email = new Control('',
  Validators.compose([Validators.required, CustomValidators.emailFormat]), CustomValidators.duplicated
);
}

Which have async validators defined in third parameter of Control() But this is not my case because im using diffrent approach.

So, my question is: is it possible to create async validator using template driven forms?

3

3 Answers

19
votes

You could try to register the provider of your async validator with the NG_ASYNC_VALIDATORS key and not the NG_VALIDATORS one (only for synchronous validators):

@Directive({
  selector: '[asyncEmailValidator]',
  providers: [
    provide(NG_ASYNC_VALIDATORS, { // <------------
      useExisting: EmailValidator, multi: true
    }),
    AccountService
  ]
})
export class EmailValidator implements Validator {
  constructor(private accountService:AccountService) {
  }

  validate(c:Control) {
    return new Promise(resolve =>
      this.accountService.getUserNames(c.value).subscribe(res => {
        if (res == true) {
            resolve(null);
        }
        else {
            resolve({validateEmailTaken: {valid: false}});
        }
    }));
  }
}

See this doc on the angular.io website:

3
votes

worth noting that the syntax has changed since then, now i am using angular 4, and here below a rewrite:

import { Directive, forwardRef } from '@angular/core';
import { AbstractControl, Validator, NG_ASYNC_VALIDATORS } from '@angular/forms';
import { AccountService } from 'account.service';

@Directive({
    selector: '[asyncEmailValidator]',
    providers: [
        {
            provide: NG_ASYNC_VALIDATORS,
            useExisting: forwardRef(() => EmailValidatorDirective), multi: true
        },
    ]
})
export class EmailValidatorDirective implements Validator {
    constructor(private _accountService: AccountService) {
    }

    validate(c: AbstractControl) {
        return new Promise(resolve =>
            this._accountService.isEmailExists(c.value).subscribe(res => {
                if (res == true) {
                    resolve({ validateEmailTaken: { valid: false } });
                }
                else {
                    resolve(null);
                }
            }));
    }
}
0
votes

I am able to correctly call validate custom validators using user service. One problem i was getting was that, I kept my custom validator inside Validators.compose(). After taking out of the compose function everything works.

import { Directive } from '@angular/core';
import { AsyncValidator, AbstractControl, ValidationErrors, NG_ASYNC_VALIDATORS, AsyncValidatorFn } from '@angular/forms';
import { Observable } from 'rxjs';
import { UserService } from '../Services/user.service';
import { map } from 'rxjs/operators';

export function UniqueUsernameValidator(userService: UserService): AsyncValidatorFn {
    return (control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> => {

        const q = new Promise((resolve, reject) => {
            setTimeout(() => {
                userService.isUsernameTaken(control.value).subscribe((data: any) => {
                    // console.log('top: ' + data + ' type: ' + typeof data);
                    if (data === false) {
                        resolve(null);
                    } else {
                        resolve({
                            usernameTaken: {
                                valid: true
                            }
                        });
                    }
                }, () => {
                    resolve({
                        usernameTaken: {
                            valid: false
                        }
                    });
                });
            }, 1000);
        });

        return q;
    };
}

@Directive({
    selector: '[appUniqueUsername]',
    providers: [{ provide: NG_ASYNC_VALIDATORS, useExisting: UniqueUsernameValidatorDirective, multi: true }, UserService]
})
export class UniqueUsernameValidatorDirective implements AsyncValidator {
    constructor(private userService: UserService) { }

    validate(control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {
        return UniqueUsernameValidator(this.userService)(control);
    }

}