0
votes

I'm trying to create a custom async validator for my registration form where it checks if an email is a valid email or not using third party API. this is ref link of the API website -

https://www.zerobounce.net/email-validation-api.html

I am trying to implement it using RxJs debounceTime, distinctUntilChanged. In the form control I have two more validations required and pattern. But I am always getting this error - Error: Expected validator to return Promise or Observable.

I have searched several examples but nothing worked. Thank you in advance.

Validator -

export class UniqueEmailValidator{
   static createValidator(_ajaxService : AjaxService){
        return (control : AbstractControl) =>{
            const apiKey = environment.emailValidatationKey;
            const baseUrl = 'https://api.zerobounce.net/v2/validate?';
            if(control.valid){
                return control
                .valueChanges
                .pipe(
                    debounceTime(800),
                    distinctUntilChanged(),
                    switchMap((email : string) => _ajaxService.apiCall('', `${baseUrl}api_key=${apiKey}&email=${email}&ip_address=''`, 'GET', true)),
                    map(res => res.json()),
                    map((validationStatus : any) => {
                        if (
                            validationStatus.status == "valid" &&
                            validationStatus.mx_found == "true"
                        ) {
                            return null
                        } else {
                            return { isEmailInvalid : true }
                        }
                    })
                )
            }
        }
    }
}

Register Component -

this.registration = this._formBuilder.group({
  firstName: new FormControl('', [
    Validators.required,
    Validators.pattern('^[a-z A-Z]+$')
  ]),
  lastName: new FormControl('', [
    Validators.required,
    Validators.pattern('^[a-z A-Z]+$')
  ]),
  email: new FormControl('', [
    Validators.required,
    Validators.pattern('[A-Za-z0-9._%-]+@[A-Za-z0-9._%-]+\\.[a-z]{2,3}')
  ],UniqueEmailValidator.createValidator(this._ajaxService))
})
2

2 Answers

0
votes

Why are you piping validators Observable from valueChanges Observable? That doesn't make much sense since the validator runs on valueChange and when it's dirty. And you are also returning the desired Observable only if the control is valid. So when sync validators mark the control as Invalid this one won't return the Observable. I guess that's what's causing the error.

Try this approach:

export class UniqueEmailValidator {
  static createValidator(_ajaxService: AjaxService) {
    return (control: AbstractControl) => {
      const apiKey = environment.emailValidatationKey;
      const baseUrl = 'https://api.zerobounce.net/v2/validate?';

      return timer(800).pipe(
        concatMap((email: string) =>
          _ajaxService.apiCall('', `${baseUrl}api_key=${apiKey}&email=${email}&ip_address=''`, 'GET', true)
        ),
        map(res => res.json()),
        map((validationStatus: any) => {
          if (validationStatus.status == 'valid' && validationStatus.mx_found == 'true') {
            return null;
          } else {
            return { isEmailInvalid: true };
          }
        })
      );
    };
  }
}}

I'm using similar approach to this one in a project where I have to check export path on the filesystem. One thing I'm not so sure about is distinctUntilChanged. Isn't that filtered already

0
votes

This what I have ended up with, which resolves my problem -

Validator -

export class UniqueEmailValidator {
    static createValidator(_ajaxService: AjaxService) {
        let validatorSubject$ = new Subject();
        let debouncedSubject$ = new Subject<string>();

        debouncedSubject$
        .pipe(debounceTime(500), distinctUntilChanged())
        .subscribe((email: string) => {
            const apiKey = environment.emailValidatationKey;
            const baseUrl = 'https://api.zerobounce.net/v2/validate?';
            _ajaxService.apiCall('', `${baseUrl}api_key=${apiKey}&email=${email}&ip_address=''`, 'GET', true).subscribe({
                next: (validationData : IEmailValidation) => {
                    if (
                        validationData.status == "valid" &&
                        validationData.mx_found == "true" &&
                        (
                          validationData.sub_status == "alias_address" ||
                          validationData.sub_status == ""
                        )
                    ) {
                        return null
                    } else {
                        return { isEmailInvalid : true }
                    }
                },
                error: (validationFailed) => {
                    console.log(
                        "Failed to validate the Email Address",
                        validationFailed
                    );
                },
            });
        });

        return (
            control: AbstractControl
        ):
        | Promise<ValidationErrors | null>
        | Observable<ValidationErrors | null> => {
            debouncedSubject$.next(control.value);
            let promise = new Promise<any>((resolve, reject) => {
              validatorSubject$.subscribe((result) => resolve(result));
            });
            return promise;
        };
    }
}

Component TS -

this.registration = this._formBuilder.group({
  firstName: new FormControl('', [
    Validators.required,
    Validators.pattern('^[a-z A-Z]+$')
  ]),
  lastName: new FormControl('', [
    Validators.required,
    Validators.pattern('^[a-z A-Z]+$')
  ]),
  email: new FormControl('', [
    Validators.required,
    Validators.pattern('[A-Za-z0-9._%-]+@[A-Za-z0-9._%-]+\\.[a-z]{2,3}')
  ],UniqueEmailValidator.createValidator(this._ajaxService))
}) 

Component HTML -

<div *ngIf="formcontrol.email.errors.isEmailInvalid">
   <p>Use a working E-mail address.</p>
</div>