SDMitch
SDMitch

Reputation: 39

Implement Async validator on Angular FormControl

I followed the short guide on Angular's website for Creating asynchronous validators that can be found at this url.

https://angular.io/guide/form-validation#implementing-a-custom-async-validator

As a result, I have this code:

import { AsyncValidator, AbstractControl, ValidationErrors } from '@angular/forms';
import { Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { CompanyService } from '../services/company/company.service';

@Injectable({ providedIn: 'root' });

export class UniqueCompanyNameValidator implements AsyncValidator {

  constructor(private companyService: CompanyService) { }

  validate(
    ctrl: AbstractControl
  ): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {
    return this.companyService.CheckName(ctrl.value).pipe(
      map(isTaken => (isTaken ? { uniqueCompanyName: true } : null)),
      catchError(() => of(null))
    );
  }
}

How do I add into my form control to actually use this custom validator? I tried to import the validator into my component and then adding it to the FormControl like the following code shows but got an error.

import { UniqueCompanyNameValidator } from '../../../shared/validators/custom.validators';

'company' : new FormControl(null, { validators: [Validators.required, Validators.minLength(2)], updateOn: 'blur' }, [UniqueCompanyNameValidator]),

The error that I got was the following:

Type 'typeof UniqueCompanyNameValidator' is not assignable to type 'AsyncValidatorFn'.
  Type 'typeof UniqueCompanyNameValidator' provides no match for the signature '(control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null>'.

(alias) class UniqueCompanyNameValidator
import UniqueCompanyNameValidator

Upvotes: 1

Views: 11215

Answers (2)

serrulien
serrulien

Reputation: 725

I must admit that the guide you linked is misleading for beginners. You could use the given service to create a directive which will inject UniqueAlterEgoValidator.

like so:

@Injectable({ providedIn: 'root' })
export class UniqueAlterEgoValidator implements AsyncValidator {
  constructor(private heroesService: HeroesService) {}

  validate(
    ctrl: AbstractControl
  ): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {
    return this.heroesService.isAlterEgoTaken(ctrl.value).pipe(
      map(isTaken => (isTaken ? { uniqueAlterEgo: true } : null)),
      catchError(() => of(null))
    );
  }
}

@Directive({
  selector: '[myValidator]',
  providers: [{provide: NG_VALIDATORS, useExisting: MyValidatorDirective , multi: true}]
})
export class MyValidatorDirective implements AsyncValidator {

  constructor(private validator: UniqueAlterEgoValidator) {}

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

With a template driven form: create a Directive that implements AsyncValidator.
With a reactive form: create a function that returns a AsyncValidatorFn.

See the angular guide about custom validators.

Upvotes: 0

Antoniossss
Antoniossss

Reputation: 32550

It must be a function/validator instance, not injectable token (class/type). In your example to make it work you have to pass validate function, not Service token. Assuming that it is this.companyService it would be like

new FormControl(null, { validators: [Validators.required, Validators.minLength(2)], updateOn: 'blur' }, [this.comapnyService.validate.bind(this.companyService)]),

or maybe (I never use that variant)

new FormControl(null, { validators: [Validators.required, Validators.minLength(2)], updateOn: 'blur' }, [this.comapnyService]),

I would create a ValidatorFn factory method that takes required service as and argument and go from there.

export class MyValidators{

  static uniqueCompanyName(companyService:SompanyService){
     return (ctrl:AbstractControl)=>{
         return companyService.doCheCkeck(ctrl.value).pipe(
            map(isTaken => (isTaken ? { uniqueCompanyName: true } : null))
         );
      }
   
  }
}

and then

'company' : new FormControl(null, { validators: [Validators.required, Validators.minLength(2)], updateOn: 'blur' }, [MyValidators.uniqueCompanyName(companyServiceInstance)]),

Upvotes: 0

Related Questions