2

In Angular 2 I am trying to create my own custom Validator.

I have created my own CustomValidators class, which implements the validator interface.

import { FormControl } from "@angular/forms";
import { MyhttpService } from "./myhttp.service";
import { Response } from "@angular/http";
import { Injectable, Directive } from '@angular/core';


@Injectable()
export class CustomValidators{

constructor(private _http : MyhttpService){

}

public api(c : FormControl)
{
    // Run 
    this._http.get("/expenses/email?email=" + c.value).subscribe((res:Response) => {
        if(res.status === 200){
            // email exists
            return null;
        } else {
            return {"email" : true}
        }
    });
}

If I make api a static method, then I can use the class successfully using.

this._formDetails = fb.group({
  "managerEmail" : ["", [Validators.required, CustomValidators.api] ]
});

However of course this is a static method so I don't have access to any of the constructor values as the constructor has not been run.

Therefore I cannot find a way of implementing custom validator with dependencies, there must be a way.

I have tried listing CustomValidators as a provider so my class receives an instantiated object of the class.

Property 'api' does not exist on type 'typeof CustomValidators'

Am I providing the class in the correct way? Why does the method not exist?

1
  • I am having the same problem now did you find a solution? Commented Feb 1, 2017 at 15:51

2 Answers 2

1

I ran into a similar situation using Angular v5.2.9 with verifying that a username does not already exist in my database. My use case is a little different—I'm using a small user list which is easily cached, and my data is centralized using the @ngrx library, but I hope it can be helpful.

Starting with the Validator class, the constructor is responsible for making the fetch request and caching the results in a static list observable; this list observable will be used by the actual validation method to see if the username is already in use.

import { Injectable } from '@angular/core'
import { FormControl } from '@angular/forms'

import { Store } from '@ngrx/store'

import { Observable } from 'rxjs/observable'
import 'rxjs/add/operator/take'
import 'rxjs/add/operator/map'

import { myActions } from '../@ngrx/actions/some.actions'
import { State, selectIds } from '../@ngrx/reducers/some.reducer'


@Injectable()
export class CustomValidators {

  static ids_in_use$: Observable<string[]>;

  constructor(
    private store: Store<State>
  ) {
    this.store.dispatch({ type: myActions.FETCH_REQUEST })
    CustomValidators.ids_in_use$ = this.store
      .select( selectIds )
      .map( ( id_list: string[] ) => id_list.map( id => id.toLowerCase() ) )
  }

  static api( control: FormControl ) {
    return new Promise(
      ( resolve ) => {
        CustomValidators.ids_in_use$
          .take( 1 )
          .subscribe(
            id_list => {
              if( id_list.indexOf( control.value.toLowerCase() ) === -1 ) 
                resolve( null )
              else resolve({ 'email-in-use': true })
            })
      })
}

In order to circumvent the lack of access to instance properties in static methods, the validator's constructor is responsible for setting the static properties. Because this class is decorated with @Injectable(), it can be dependency-injected to the constructor of the component that's using it:

constructor(
  ...,
  private fb: FormBuilder,
  private customValidators: CustomValidators
) { }

This is how I'm able to ensure that the code in the validator's constructor is executed despite the main validation logic being a static method. Similarly, I would imagine that you could use this instance to make use of any instance properties/methods you specifically need prior to the validation—in your case, making the http request. I can then use the static validation method in the FormBuilder group (bear in mind, unless you make a call to it, your tslint will warn you that 'customValidators' is declared but its value is never read)

this._formDetails = fb.group({
  'managerEmail': [ '', Validators.required, CustomValidators.api ]
})

Finally, a provider must be declared for the injectable service, which can be done in the @Component decorator:

@Component({
  ...,
  providers: [ CustomValidators ]
})
Sign up to request clarification or add additional context in comments.

Comments

0

Try this:

this._formDetails = fb.group({
  "managerEmail" : ["", Validators.required, CustomValidators.api]
});

Validators.required is a synchronous validator, but your CustomValidators.api is an asynchronous validator.

Per the official documentation, each form control should be specified with state, synchronous validator(s), then async validator(s).

1 Comment

Your answer contains a mistake: this._formDetails = fb.group({ "managerEmail" : ["", Validators.required], [CustomValidators.api] }); is what you meant. But that is not the answer to his question.

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.