Utilizing a custom asynchronous validator in Angular 8 that returns a Promise or Observable for validation purposes

I am currently working on developing a custom async validator for my registration form. The purpose of this validator is to verify if an email is valid or not by utilizing a third-party API. Here is the reference link to the API website -

My approach involves implementing this validator using RxJs debounceTime and distinctUntilChanged. In addition to checking email validity, there are two other mandatory validations required in the form control. However, I keep encountering the following error - Error: Expected validator to return Promise or Observable.

I have explored various examples but none seem to address this issue effectively. Thank you for any assistance provided 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))
})

Answer №1

Have you ever wondered why validators Observable is being piped from valueChanges Observable? It seems counterintuitive since the validator operates on value changes and when it's dirty. Moreover, the desired Observable is only returned if the control is valid. This means that if sync validators mark the control as invalid, the Observable won't be returned, potentially leading to errors.

Here's a different approach to consider:

class EmailValidator {
  static create(apiService: ApiService) {
    return (control: Control) => {
      const apiKey = config.key;
      const url = 'https://api.example.com/validate?';

      return timer(800).pipe(
        concatMap((email: string) =>
          apiService.call('', `${url}key=${apiKey}&email=${email}&ip=''`, 'GET', true)
        ),
        map(res => res.json()),
        map((status: any) => {
          if (status.status == 'valid' && status.mx_found == 'true') {
            return null;
          } else {
            return { isInvalidEmail: true };
          }
        })
      );
    };
  }
}}

I've applied a similar approach in a project where I needed to validate export paths in the filesystem. One question I have is about distinctUntilChanged. Isn't that already filtered?

Answer №2

This is the solution I came up with to address my issue -

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>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Tips for transforming promise function into rxjs Observables in Angular 10

As a beginner in typescript and angular, I am trying to understand observables. My query is related to a method that fetches the favicon of a given URL. How can I modify this method to use observables instead of promises? getFavIcon(url: string): Observ ...

Make sure to not update until all the necessary checks have been completed successfully

My goal is to update an array of objects only after all the necessary checks have passed. I have one array of objects representing all the articles and another array of objects representing the available stock. I want to make sure that all the articles ar ...

What are the most effective strategies for managing vast amounts of data using JS fetch?

The server is taking about 2 minutes to export a large JSON data, resulting in a timeout error on the client side before receiving a response from the server. I have searched online for solutions, but I cannot find a way to extend the timeout or continue ...

Troubleshooting: React is not defined in Rollup + React 17 with updated JSX Transform

I'm currently working on prototyping a microfrontend architecture using Rollup and multiple create-react-app applications. However, when I try to locally yarn link my external app with the container app, I am encountering the following error: The err ...

Turn off Affix feature for mobile phones

Hello, I created a sidebar and used some JavaScript to automatically update its width in relation to its parent container. Now, I want the sidebar to be removed automatically when the window size goes below 750px (which I believe is the tablet breakpoint i ...

Stubbing out a module's function with Sinon

Let's envision a scenario where there is a file present: // app.js const connection = require('./connection.js') module.exports = function (...args) { return async function (req, res, next) { // code implementation ... const ...

Error occurs in Angular Mat Table when attempting to display the same column twice, resulting in the message "Duplicate column definition name provided" being

What is the most efficient method to display a duplicated column with the same data side by side without altering the JSON or using separate matColumnDef keys? Data: const ELEMENT_DATA: PeriodicElement[] = [ {position: 1, name: 'Hydrogen', wei ...

Experiment with parsing multiple outputs instead of repeatedly coding them as constants in Node.js

In my application, I am parsing multiple database data using cron and currently, I have it set up to create a const run for each data to find the dag_id and manually test the determineCron function one at a time. How can I create an array so that the "dete ...

The text entered in the textbox vanishes after I press the submit button

When a user selects a value in a textbox and clicks the submit button, the selected value disappears. <div class="panel-body" ng-repeat="patient in $ctrl.patient | filter:$ctrl.mrd"> <form> <div class="form-group"> ...

How can I display a "loading..." message as a temporary placeholder while waiting for my Apexcharts to load?

I've spent a day trying to solve this issue but couldn't find a solution. Any help would be greatly appreciated. Recently, I was working on creating a cryptocurrency tracker in React. I successfully built a table that displays multiple currencie ...

How to use TypeScript to filter an array based on the values of another array

Suppose I have two arrays. The first one looks like this: names: [{ value: 'recordedData', desc: 'Data' } { value: 'recordedNumbers', desc: 'numbers' } { value: 'recordedNames', desc: 'name ...

Exploring different pages in Angular2 and Ionic2

I am encountering difficulties when it comes to navigating between pages in Angular2 / Ionic2. When attempting to navigate to a new page using the code below: import {Page, NavController} from 'ionic-angular'; import {HomePage} from '../ho ...

Merge text inputs to preview content prior to form submission

I've been on the hunt for a solution to display the collective values entered into multiple text box fields as they are being typed. Currently, I have 6 text boxes (description1, description2, description3, description4, description5, description6) wh ...

Incorporating two components and nesting one within the other, similar to the way angular-material seamlessly integrates its components

I recently discovered the angular-material module and I'm a bit perplexed about how it allows multiple components to be used inside one another in the same place. Take a look at the example below: <mat-drawer-container class="example-container"> ...

The function of the Angular ng-include form action for posting a URL appears to be malfunctioning

When a form post is included in a parent HTML using ng-include, it does not trigger the submission action. <div ng-include src="'form.html'"></div> The code in the form.html file is as follows: <form action="next/login" method = ...

Why is my Angular router displaying the page twice in the browser window?

Angular was initially loading the page on the default port localhost:4200. I wanted it to serve as localhost:4200/specialtyquestions when the app builds, and that is working, but the pages are appearing twice in the browser. Any ideas on what might have be ...

axios replicates the URL within the interceptor

I am currently using Axios for my HTTP requests, and I have encountered an issue for the first time. The initial request goes through smoothly, but on the second request, Axios is inexplicably duplicating my URL. The URL that Axios is attempting to fetch ...

Getting rid of the scrollbar in Internet Explorer

Instead of just removing the scrollbar, I want to ensure that scrolling capabilities remain intact. This is important because I want to create a 'slide show' effect on the website where users can click 'next' and scroll through content ...

How can one change the color of movable tiles to red while keeping the rest of the tiles in their original state in a puzzle game?

In this section of our code, the function moveTile is responsible for moving a tile. How can I modify it so that the hover color changes to red? function moveTile(identity) { var emptyId = findEmptySpace(); if (isEmptyNeighbor(identity)) { document.ge ...

Error in AWS Lambda: Module 'index' not found

In my setup, I have kept it simple by using typescript. All my typescript files are compiled into a /dist directory. Debugging with Webstorm is smooth as it easily finds the handler: https://i.sstatic.net/qkxfD.png The problem arises when I try to run i ...