Supersoaker
Supersoaker

Reputation: 61

Form field async validation not performing HTTP call

I'm creating an Angular+NodeJS+SQlite app. On one of my views i need to introduce an IP in a reactive form. That IP should be unique in the database so I'm using an async validator to check every time a character is introduced in the input if that value is in the DB.

Following angular documentation and some videos I've managed to assemble this code:

Definition of the form control:

createForm() {
    this._addNodoForm = this._formBuilder.group({
        name: [this.nodo.name,[Validators.required]],
        ip: [this.nodo.ip,[Validators.required, uniqueIPValidator(this._nodeService)]],
        status: [this.nodo.status, [Validators.required, Validators.maxLength(1)]],
        vbus_ip: [this.nodo.vbus_ip, [Validators.required]],
        physical_address: [this.nodo.physical_address, [Validators.required]],
        tunnel_address: [this.nodo.tunnel_address, [Validators.required]],
        transfer_rate: [this.nodo.transfer_rate, [Validators.required, Validators.max(9600), Validators.min(0)]],
    });

}

Definition of the validation class:

export function uniqueIPValidator(nodeService: NodeService): AsyncValidatorFn {
  return (c: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> => {
    if(c.value!=''){
        return nodeService.getUniqueIp(c.value).pipe(map(
          (addresses :any)=> {
            return (addresses && addresses > 0) ? {"uniqueIP": true} : null;
          }));
    }
  }
}

  @Directive({

    selector: '[uniqueIP]',
    providers: [{ provide: NG_ASYNC_VALIDATORS, useExisting: UniqueIpValidatorDirective, multi: true }]
  })


  @Injectable()
  export class UniqueIpValidatorDirective implements AsyncValidator{
    constructor(private nodeService: NodeService) { }
    validate(ctrl: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {
      return uniqueIPValidator(this.nodeService)(ctrl);
    }
  }

nodeService.getUniqueIp(): This method returns a the http response consisting in the ips that match the control value. The method works perfectly using .subscribe()

 getUniqueIp(ip:string):Observable<any>{
        return this._http.get(this.url + 'unique-ip/' + ip);
    }

And finally the html code of the input:

<input type="text" formControlName="ip" class="form-control" uniqueIP>
<div *ngIf="_addNodoForm.get('ip').errors?.uniqueIP">IP must be unique</div>

The problem is that the HTTP call is not even performed using .pipe(map( it does not reach the API rest method that retrieves the ips from the DB. I've tried using a subscription system and it does actually retrieve the ips but i dont think thats how it is supposed to return the Observable<ValidationsErrors> so its not showing the error on the form either.

export function uniqueIPValidator(nodeService: NodeService): AsyncValidatorFn {
  return (c: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> => {
    if(c.value!=''){//to avoid making the http call when the input is empty
        return nodeService.getUniqueIp(c.value).pipe().
          subscribe(addresses => {
            console.log('DIR: '+addresses);
            if(addresses!=null){
              return {"uniqueIP": true};
            }
          },error=>{
            console.log(error);
          });
    }
  }
}

I know from theory that Async validators are only fired when the sync validators return null but i dont really know what that means in this case, could that be a problem? This is just for educational porpuses as I'm trying to understand async validators so i can use them in the future. Any suggestions about the problem or the post itself are appreciated.

Upvotes: 0

Views: 645

Answers (1)

Darren Ruane
Darren Ruane

Reputation: 2505

If you look at the signature for FormControl in the docs you will see that it's constructor parameters are:

formState: any = null, 
validatorOrOpts?: ValidatorFn | AbstractControlOptions | ValidatorFn[], 
asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[]

I separated the parameters onto their own individual lines to emphasize that there are three of them.

I believe your issue may be that you are mixing in your uniqueIPValidator AsyncValidatorFn with the synchronous Validators.required ValidatorFn.

Try changing:

ip: [this.nodo.ip,[Validators.required, uniqueIPValidator(this._nodeService)]],

to

ip: [this.nodo.ip, Validators.required, uniqueIPValidator(this._nodeService)],

By doing this you will be providing the uniqueIPValidator(this._nodeService) parameter as the third parameter (the one for async validators) instead of inside of the array of the second parameter.

Upvotes: 1

Related Questions