14

I'm trying to make a custom Angular 2 form Validator to check if a user exist on a data base.

This is the code of my custom form Validator

import { FormControl } from '@angular/forms';
import {API} from "../services/api";
import {ReflectiveInjector} from "@angular/core";

export class EmailValidator {

  constructor() {}

  static checkEmail(control: FormControl,): any {
    let injector = ReflectiveInjector.resolveAndCreate([API]);
    let api = injector.get(API);

    return api.checkUser(control.value).then(response => {
      response;
    });

  }

}

And this is this is my custom service which is responsible for make the request to a node api on backend

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/toPromise';

@Injectable()
export class API {
  private backendUrl = 'http://127.0.0.1:5000/api/register/';

  constructor(private http: Http) { }

  checkUser(email:string): Promise<any> {
    return this.http.get(this.backendUrl + email)
      .toPromise()
      .then(response => response.json())
      .catch(this.handleError);
  }

When I try to validate a user this is the error that is showed

EXCEPTION: Error in ./TextInput class TextInput - inline template:0:0 caused by: No provider for Http! (API -> Http)

What I'm doing wrong?

Thanks

Hanzo
  • 1,839
  • 4
  • 30
  • 51

1 Answers1

23
@Injectable()
export class EmailValidator {

  constructor(private api:API) {}

  /* static */ checkEmail(control: FormControl,): any {
    return this.api.checkUser(control.value).then(response => {
      response;
    });
  }
}

Add it to of @NgModule() or @Component() depending on what scope you want it to have

providers: [EmailValidator]

Inject it to the component where you want to use it

export class MyComponent {
  constructor(private emailValidator:EmailValidator, fb:FormBuilder){}

  this myForm = fb.group({
    email: [], [this.emailValidator.checkEmail.bind(this.emailValidator)]
  });
}
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 1
    Thanks!, you save my day. I didn't know how to use a custom service on this way into the `formBuilder.group` – Hanzo Mar 07 '17 at 11:13
  • Sorry one more question. On the `EmailValidator` if the mail exist, what would be to return? I mean that `return this.api.CheckUser(control.value).then(response => { if(response.msg === "Exist") { response; } else { null; } }); }` In both cases the validations is succesful, What would be to return if the user exist? – Hanzo Mar 07 '17 at 11:38
  • 1
    For example `return { error: 'User exists'};` or whatever else information you want to get in case of an error. – Günter Zöchbauer Mar 07 '17 at 12:00
  • Thanks you're right. Definitely today is not my day. – Hanzo Mar 07 '17 at 12:39
  • 2
    Great solution, especially binding the validator. – Faiz Mohamed Haneef Apr 13 '18 at 13:08
  • Injecting the validator into the constructor of your component has one downside: you're creating a singleton with respect to the component, so the validator's state will be reused on every control it's bound to. In practice, this means if one control is declared invalid, the other control is too. – marked-down May 31 '21 at 23:53
  • @ReactingToAngularVues I'd say if your validator has state, you're doing something wrong and should reconsider your design. – Günter Zöchbauer Jun 01 '21 at 06:06
  • 1
    @GünterZöchbauer Well it'd be nice if `ReactiveFormsModule` provided a way to query the validators on a `FormControl`, and provided a way of determining which specific `AsyncValidators` were `PENDING`, but there isn't, so we end up in a situation where validators have state. – marked-down Jun 01 '21 at 09:15
  • @ReactingToAngularVues I see. Forms are still lacking :-/ – Günter Zöchbauer Jun 02 '21 at 04:23