Angular async validators making multiple http calls

56 views Asked by At

I have a custom async validator in Angular app that validates the uniqueness of a value. The validator makes HTTP calls to the backend API. I am trying to control the number of API calls by adding distinctUntilChanged and debounceTime, but that doesn't seem to be working fine. It is making call for everykey stroke.

Where am I going wrong?

I tried using valueChanges of the control and things work fine, but I have a use case where I need to run all validators to check for error by clicking a single button on the form, is there a way to run all validators on the form irrespective of value changes?

With my current approach I am able to run all validations by clicking a single button, but it doesn't obey RxJS operators.

function uniqueAltIdValue(type, productId): AsyncValidatorFn {
    return (control: AbstractControl): Observable<ValidationErrors | null> => {
        const value: string = control.value;

        return validateValue(type, value).pipe(
            distinctUntilChanged(),
            debounceTime(800),
            map((response: { id: string; isValid: boolean }) =>
                (productId.toLowerCase() !== response.id.toLowerCase() && !response.isValid)
                    ? { valueExists: true }
                    : null,
            ),

            catchError(() => of({ valueExists: true })),
        );
    };
}

function validateValue(type: string, value: string): Observable<{ id: string; isValid: boolean }> {
    return this._httpService.requestCall(`${this.url}?type=${type}&id=${value}`);
}
1

There are 1 answers

0
martin On

The function returned from uniqueAltIdValue() is called every time the form runs validators so in your case you're creating a separate chain each time. In turn, this means that each time a validator is executed you're creating a new Observable and that's why distinctUntilChanged() or debounceTime() have no effect in your case.

What you can do instead is returning a Subject and then pushing values into it when you need to run validation. You also want to call validateValue() after deboucneTime() and distinctUntilChanged().

function uniqueAltIdValue(type, productId): AsyncValidatorFn {
    const subject = new Subject();
    const validate$ = subject.pipe(
        distinctUntilChanged(),
        debounceTime(800),
        switchMap(() => validateValue(type, value)),
        map((response: { id: string; isValid: boolean }) =>
                (productId.toLowerCase() !== response.id.toLowerCase() && !response.isValid)
                    ? { valueExists: true }
                    : null,
        ),
        catchError(() => of({ valueExists: true })),
    )

    return (control: AbstractControl): Observable<ValidationErrors | null> => {
        subject.next(control.value);
        return validate$;
    };
}

Note, that I didn't test the code, but I'm sure you'll get the point.

I'm suspicious that distinctUntilChanged() maybe even shouldn't be there because Angular's forms might wait until all validators emit and if distinctUntilChanged() ignores values it might break validation (but I'm not completely sure about this so maybe it's fine).