17

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 Answers 3

20

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:

Sign up to request clarification or add additional context in comments.

4 Comments

Thank you! This is exactly what I've wanted. Btw. Is it possible to combine async part of validator (promise) with noasync part (just regex) in one validator class in such a way that async part will be started only if regex is OK or do I need both of them separately?
Yes you can but you need in both cases to resolve the promise. The latter can be resolved even if processing isn't asynchronous...
Ok, I'll try it. Thanks once more.
Thank you for building on the existing documentation at angular.io/guide/form-validation this helps so much!
3

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);
                }
            }));
    }
}

Comments

0

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);
    }

}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.