Reputation: 29
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?
Upvotes: 2
Views: 1635
Reputation: 11
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 ]
})
Upvotes: 1
Reputation: 143
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).
Upvotes: 0