24

How can I debounce a function which gets called on an "keyUp" event?

Here is my code:

My Function

private handleSearch(searchTextValue: string, skip?: number): void {
    this.searchTextValue = searchTextValue;
    if (this.skip === 0 || typeof skip === "undefined") {
        this.skip = 0;
        this.pageIndex = 1;
    } else {
        this.skip = skip;
    }
    this.searchTextChanged.emit({ searchTextValue: searchTextValue, skip: this.skip, take: this.itemsPerPage });
}

My HTML

<input type="text" class="form-control" placeholder="{{ 'searchquery' | translate }}" id="searchText" #searchText (keyup)="handleSearch(searchText.value)">

Bassically what I'm trying to achieve is that handleSearch gets called a few moments after the user stop typing.

I found out i can use lodash's _debounce() for this, but I haven't found out how to put this on my keyUp event.

Nicolas
  • 4,526
  • 17
  • 50
  • 87

3 Answers3

55

UPDATE: Using RXJS 6 pipe operator:

this.subject.pipe(
  debounceTime(500)
).subscribe(searchTextValue => {
  this.handleSearch(searchTextValue);
});

You could create a rxjs/Subject and call .next() on keyup and subscribe to it with your desired debounceTime.

I'm not sure if it is the right way to do it but it works.

private subject: Subject<string> = new Subject();

ngOnInit() {
  this.subject.debounceTime(500).subscribe(searchTextValue => {
    this.handleSearch(searchTextValue);
  });
}

onKeyUp(searchTextValue: string){
  this.subject.next(searchTextValue);
}

HTML:

<input (keyup)="onKeyUp(searchText.value)">
Ploppy
  • 14,810
  • 6
  • 41
  • 58
  • how can I pass parameters to `ngOnInit() { this.subject.debounceTime(500).subscribe(res => { //do something }); }`? – Nicolas Mar 13 '17 at 10:31
  • You can pass your search query to onKeyUp() then pass it to the .next() and then it would be the 'res' variable in the subscription. Make sure to change the subject from Subject to Subject as you are working with a string. Let me edit the answer. – Ploppy Mar 13 '17 at 10:33
  • and if I have 1 optional parameter? should i put them in an object? In the OP I rely on `searchTextValue` and `skip` which is optional – Nicolas Mar 13 '17 at 10:39
  • I edited it again. About skip, just create a skip variable in your component and use it in the handleSearch method like this: if(this.skip) – Ploppy Mar 13 '17 at 10:44
  • I missed something in your solution, so srry for the dumb questions about the parameters! It works! – Nicolas Mar 13 '17 at 10:50
  • No problem, don't worry ;) – Ploppy Mar 13 '17 at 10:54
  • 1
    Don't forget to import 'rxjs/add/operator/debounceTime' – Téwa Mar 19 '18 at 15:01
  • @Ploppy this is just so so beautiful T_T – reckface Apr 26 '18 at 15:48
  • When I try this, I'm also getting a delay on the enter key as well, how to avoid? If they stop typing for 1 second, it's firing correctly, but if I hit enter, it's still taking 1 second to fire. I have (keyup)="keyUpFunction($event);" (enter.keyUp)="enterFunction(event)".. problem is enter is also firing generic keyup. – Dan Chase Sep 03 '21 at 18:07
23

An Update for Rx/JS 6. Using the Pipe Operator.

import { debounceTime } from 'rxjs/operators';

this.subject.pipe(
      debounceTime(500)
    ).subscribe(searchTextValue => {
      this.handleSearch(searchTextValue);
    });

Everything else is the same

CodeMonkey
  • 1,136
  • 16
  • 31
Arron McCrory
  • 690
  • 7
  • 15
2

Take a look at answer here: https://stackoverflow.com/a/35992325/751200

And article here: https://blog.thoughtram.io/angular/2016/01/06/taking-advantage-of-observables-in-angular2.html (quite old, but still good).

Basically, you can use an Observable object provided by Angular Forms.

For example, this.myFormGroup.get("searchText").valueChanges.pipe(debounceTime(500), distinctUntilChanged()).subscribe(...)

If you need to perform an HTTP request when user stops typing, you can place such HTTP call into switchMap operator and add it to pipe list:

this.myFormGroup.get("searchText")
                .valueChanges
                .pipe(debounceTime(500),
                      distinctUntilChanged(), 
                      switchMap((value: string) => {
                          return this.service.getData(value);
                      }))
                .subscribe((valueFromRest) => { ... });

The magic in switchMap will automatically cancel the previous HTTP request (if it's not completed yet) and start a new one automatically.

kolobok
  • 3,835
  • 3
  • 38
  • 54