0

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.

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Exactly what I needed, thanks! Guess i have to read again the form docs.
You are very welcome! I am glad it solved your problem. It was a very easy mistake to make to be honest :)

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.