Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What to return in the angular 2 async validator when using observables

What do I have to return in the customerNameValidator if the

async validation fails/succeeds that my 'customerName' FormControl is invalid?

this.customerForm = this.formBuilder.group({
customerName: 
[this.newCustomerName, [Validators.minLength(2), Validators.required],[this.customerNameValidator.bind(this)]]
});


customerNameValidator(c: AbstractControl)
{
   return this.service.customerExists(c.value,this.companyId).subscribe(response =>
   {
        if(response == true)
        {
             alert("true");
        }
        else
        {
            alert("false");
        }
   });
}
like image 802
Elisabeth Avatar asked Feb 21 '17 22:02

Elisabeth


People also ask

What is async validator in angular?

Angular does not provide built-in type async Validation implmentation, it provides only for sync validation. The implementation of async validator is very similar to the sync validator. The only difference is that the async Validators must return the result of the validation as an observable or as Promise.

What is AsyncValidator?

AsyncValidator Interface AsyncValidator has a method validate that has argument AbstractControl and it will contain latest value of the form control. The validate method will return either Promise<ValidationErrors | null> or Observable<ValidationErrors | null> .

What is AbstractControl in angular?

Descriptionlink. It provides some of the shared behavior that all controls and groups of controls have, like running validators, calculating status, and resetting state. It also defines the properties that are shared between all sub-classes, like value , valid , and dirty . It shouldn't be instantiated directly.


2 Answers

Rather than subscribing, you should map the observable to change the result of the returning stream, rather than reading from it.

customerNameValidator(c: AbstractControl)
{
   return this.service.customerExists(c.value,this.companyId).map(response =>
   {
        if(response == true)
        {
            return { customerExists: true };
        }
        else
        {
            return;
        }
   });
}

Returning an object with a value that is true is how you should return the observable. You may be missing some important steps for async validators though, but because we don't gave all your code it's hard to say. Try checking out this article or this article for more information.

like image 200
Adam Avatar answered Sep 28 '22 01:09

Adam


I implemented an reactive form with an AsyncValidatorFn on angular 6.1.1. and would like to share some of my learning

I found out that angular does not (automatically) update the form control for AsyncValidatorFn as it does for internal sync validators .

so, as per the "AsyncValidatorFn" interface spec, you have to "manually" update your form control in your implementation of

(c: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null>;

and then, you will check the control state in the html element

What I had implemented is an Username existence checking that might be very commonly found in an user sign-up process

below are the code excerpts:

the form control

 // Supports alphabets and numbers no special characters except underscore('_') and dash('-') min 3 and max 20 characters.
    this.userName = new FormControl('', Validators.compose([Validators.required, Validators.pattern('^[A-Za-z0-9_-]{3,20}$')]),Validators.composeAsync([this.checkUser()]));

the custom async validator and the helper functions

checkUser (): AsyncValidatorFn{

    return (c: AbstractControl): Observable<ValidationErrors> => {
      return c
        .valueChanges
        .debounceTime(400)
        .mergeMap(value => this.gabriel.filter({'userName':value}))
        .map(stat => this.mapErr(c, stat));
    } 

  }

  private mapErr(c: AbstractControl, res: any): ValidationErrors{
    let err: ValidationErrors;
    switch (res['state']){
      case  0:
        err = null;
        break;
      case -100:
        err = {'existed': true};
        break; 
      case -1:
      default:
        err = {'failed': true};                      
    }
    c.setErrors(err);
    return err;
  }

note that I input the control as a parameter into the "mapErr" function, and set the control by "c.setErrors(err);".

the "return err;" statement return "ValidationErrors" as per the "AsyncValidatorFn" interface spec.

the "gabriel.filter()"queries the backend with the extracted username; and returns 0, -100, -1 respectively for "ok", "duplicated", and "operation failed"

  filter(json): Observable<{}>{
    let body = JSON.stringify(json);
    let headers = new Headers({'Content-Type': 'application/json'});
    let options = new RequestOptions({ headers: headers });
    return this.http.post(Cons.filter, body, options).timeout(10000).map((res:Response) => res.json());
  }

the control checking in html file

 <form [formGroup]="sf" (ngSubmit)="signin()">
          <ion-item>
            <ion-label>UserName</ion-label>
            <ion-input type="text" formControlName="userName" [class.invalid]="userName.dirty&&userName.invalid&&userName.errors!=null" ></ion-input>
          </ion-item>
            <p *ngIf="userName.dirty && userName.hasError('existed')">
              Username already existed
            </p>
            <p *ngIf="userName.dirty && userName.hasError('failed')">
              can not check validity of Username 
            </p>

I also found out that the async validators are not trigger until sync validator are satisfied in one form control.

in my case, I also used the built-in Validators.pattern to define a minimum length of 3.(see above Username formControl definition)

the custom async validator never triggers as long as my input length is shorter than 3.

like image 33
George Wang Avatar answered Sep 28 '22 03:09

George Wang