Salvatore Iovene
Salvatore Iovene

Reputation: 2323

How can I debounce this Angular 7 async validator that uses combineLatest internally?

I sort of understand that the tools involved in this would be pipe, timer, switchMap, debounceTime, distinctUntilChanged, and possibly more, but I cannot figure out how to make them work with combineLatest.

The following validator gets a comma-separated list of emails, and performs an API call for each of them to check if that email exists in the customers database.

import {AbstractControl, AsyncValidatorFn, ValidationErrors, ValidatorFn} from "@angular/forms";
import {combineLatest} from "rxjs";
import {Observable} from "rxjs/internal/Observable";

import {UtilityApiService} from "../../services/api/utility-api.service";

/**
 * The email address must exist in the database and belong to a customer.
 * It works both with a single email or a comma separate list of emails. In case of a list, it will only validate
 * as a success if ALL emails belong to a customer.
 * @returns {ValidatorFn}
 * @constructor
 */
export class CustomerEmailCsvValidator {
    public static createValidator(internalApiService: UtilityApiService): AsyncValidatorFn {
        return (control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> => {
            return new Observable((observer) => {
                if (!control.value) {
                    observer.next(null);
                    return;
                }

                const emails: string[] = control.value.split(",").map(email => email.trim());
                const observables: Observable<boolean>[] = emails.map(email => {
                    return internalApiService.isCustomerEmail(email);
                });

                combineLatest(observables).subscribe((responses: boolean[]) => {
                    if (responses.every(value => value)) {
                        // All emails exist and belong to customers. Therefore no error.
                        observer.next(null);
                    } else {
                        // One or more emails do not exist in the database, or do not belong to a customer.
                        // This is an error for this validator.
                        observer.next({customerEmail: true});
                    }
                    observer.complete();
                });
            });
        };
    }
}

How can I debounce so that it doesn't run the API calls more than once in 750 ms?

Upvotes: 2

Views: 2562

Answers (1)

Salvatore Iovene
Salvatore Iovene

Reputation: 2323

This is what ended up working for me:

import {AbstractControl, AsyncValidatorFn, ValidationErrors, ValidatorFn} from "@angular/forms";
import {combineLatest, of, timer} from "rxjs";
import {Observable} from "rxjs/internal/Observable";
import {map, switchMap} from "rxjs/operators";

import {UtilityApiService} from "../../services/api/utility-api.service";

/**
 * The email address must exist in the database and belong to a customer.
 * It works both with a single email or a comma separated list of emails. In case of a list, it will only validate
 * as a success if ALL emails belong to a customer.
 * @returns {ValidatorFn}
 * @constructor
 */
export class CustomerEmailCsvValidator {
    public static createValidator(internalApiService: UtilityApiService): AsyncValidatorFn {
        return (control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> => {
            return timer(750).pipe(switchMap(() => {
                if (!control.value) {
                    return null;
                }

                const emails: string[] = control.value.split(",").map(email => email.trim());
                const observables: Observable<boolean>[] = emails.map(email => {
                    return internalApiService.isCustomerEmail(email);
                });

                return combineLatest(observables).pipe(map((responses: boolean[]) => {
                    if (responses.every(value => value)) {
                        // All emails exist and belong to customers. Therefore no error.
                       return null;
                    } else {
                        // One or more emails do not exist in the database, or do not belong to a customer.
                        // This is an error for this validator.
                        return {customerEmail: true};
                    }
                }));
            }));
        };
    }
}

Upvotes: 1

Related Questions