Rohit
Rohit

Reputation: 3146

Debouncing HTTP requests in Angular

I've built a loading spinner component in Angular 2 that I'd like to trigger before http requests are made and disable when they're done. The problem is that each time the user changes an input (checks of of a box, types into the input box), the http request fires. This means a lot of requests, and the overlay is coming up constantly. I'd like to wait a set period of time (half a second?) after an input is triggered before triggering the http request, giving the user time to put in other inputs. I've read up a bit on debounce, but as far as I can see, that's for time waiting before making another request? And as far as I can see, that's just a buffer time between requests.

Basically, right now, I have a component that handles my inputs. When an input is changed (checkboxes right now), the following code is triggered:

@Output() filtersChanged = new EventEmitter();
emitFilters(): void {
    this.filtersChanged.emit(this.filters);
}

Which through an intermediary step, sets off my http request:

getEvents(filters): Observable<Event[]> {
    this.loadingSpinnerService.showLoadingSpinner();
    let params: URLSearchParams = new URLSearchParams();
    params.set('types', filters.types.join(','));
    params.set('dates', filters.dates.join(','));
    return this.http
        .get('//api.dexcon.local/getEvents.php', { search: params })
        .map((response: Response) => {
            return response.json().events;
        });
}

In Angular 1, I would have put it in a timeout which refreshed each time a user affected an input, so it'd trigger a set time after the final input was touched. Is this the best way to do it in Angular 2 as well? From my reading, debounce locks out a request from happening too close to a second request, but I'm wondering how to best prevent a request from happening after an action is taken for a given period of time.

Upvotes: 9

Views: 5455

Answers (2)

Fredrik_Borgstrom
Fredrik_Borgstrom

Reputation: 3228

You should be able to solve this by decorating your getEvents method with a debounce function:

    function debounce(ms: number) {

        let timeoutId;

        return function (target: Object, propName: string, descriptor: TypedPropertyDescriptor<any>) {
            let originalMethod = descriptor.value;
            descriptor.value = function (...args: any[]) {
                if (timeoutId) return;
                timeoutId = window.setTimeout(() => {
                    timeoutId = null;
                }, ms);
                return originalMethod.apply(this, args);
            }
        }
    }

And then simply apply it to your method like this (set the decorator parameter to your desired debounce time in ms):

@debounce(300)
getEvents(filters): Observable < Event[] > {
    ...
}

Upvotes: 0

Jesse Carter
Jesse Carter

Reputation: 21147

The easiest way to accomplish what you're after is to set up a Subject (I'm assuming you have access to Rxjs here). Initialize one in your component:

inputSubject: Subject<string> = new Subject<string>();

Since a Subject is both an observer and an observable, you're going to want to set up a subscription to listen for changes. Here's where you can apply your debounce.

this.subscription = this.inputSubject.asObservable()
    .debounceTime(1000)
    .subscribe(x => this.filtersChanged.emit(this.filters));

Now, in your emitFilters() function instead of directly emitting, push the value onto the Subject.

this.inputSubject.next(newValue);

Don't forget to store your subscription as a member of your component class and dispose of it properly in your OnDestroy().

ngOnDestroy() {
    if (this.subscription) {
      this.subscription.unsubscribe();
    }
}

Upvotes: 6

Related Questions