admin管理员组

文章数量:1356588

I have an update() method, with which I search for certain entries in a table and update/filter the entries for each keystroke inside an input field.

My goal is to wait about 400ms after each keystroke before firing the request into the backend so that I avoid too many meaningless requests.

Currently I have implemented it with the setTimeout() function, but I am sure that there is a more elegant way with RxJS.

  update(searchInput: string) {
    setTimeout(() => {
      this.myService.search(searchInput)
      .subscribe((res) => {
        this.myArray.content = res;
      });
    }, 400);
  }

Does somebody has any idea?

I have an update() method, with which I search for certain entries in a table and update/filter the entries for each keystroke inside an input field.

My goal is to wait about 400ms after each keystroke before firing the request into the backend so that I avoid too many meaningless requests.

Currently I have implemented it with the setTimeout() function, but I am sure that there is a more elegant way with RxJS.

  update(searchInput: string) {
    setTimeout(() => {
      this.myService.search(searchInput)
      .subscribe((res) => {
        this.myArray.content = res;
      });
    }, 400);
  }

Does somebody has any idea?

Share Improve this question asked Dec 18, 2019 at 12:53 Codehan25Codehan25 3,01412 gold badges59 silver badges109 bronze badges 2
  • you need to use debounce instead – demkovych Commented Dec 18, 2019 at 12:55
  • 1 angular.io/guide/… – yurzui Commented Dec 18, 2019 at 13:03
Add a ment  | 

6 Answers 6

Reset to default 3

What you are looking for is debounceTime. It waits for x miliseconds before emitting anything. Combining it with other operators to not overload your API would be a good choice.

Your observable would look like

const search$ = fromEvent(search, 'input').pipe(
  debounceTime(400), // Wait 400 MS before outputting
  distinctUntilChanged(), // Only output unique values
  map(event => event.target.value), // Extract the value of the search
  switchMap((search) => service.doApi(search)) // SwitchMap to cancel a previous search if it wouldn't have pleted
)
search$.subscribe()  // These might leak. Keep them in an array and clean them up when the ponent unloads

Where the search element would be a viewChild of your ponent.

import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { fromEvent, Subscription } from 'rxjs';
import { debounceTime, distinctUntilChanged, map, switchMap } from 'rxjs/operators';

@Component({
  selector: 'app-ponent',
  templateUrl: './app.ponent.html',
  styleUrls: ['./app.ponent.scss']
})
export class AppComponent implements OnInit, OnDestroy {
  @ViewChild('yourInput', {static: true}) search: ElementRef;

  searchSubscription: Subscription;

  ngOnInit(): void {
    const search$ = fromEvent(this.search.nativeElement, 'input').pipe(
      debounceTime(400), // Wait 400 MS before outputting
      distinctUntilChanged(), // Only output unique values
      map((event: KeyboardEvent) => (event.target as HTMLInputElement).value), // Extract the value of the search
      switchMap((search) => service.doApi(search)) // SwitchMap to cancel a previous search if it wouldn't have pleted
    )  // Lives forever
    this.searchSubscription = search$.subscribe()
  }

  ngOnDestroy(): void {
    this.searchSubscription.unsubscribe()
  }
}


I've leave some ments in the code:

const search = fromEvent(searchInput, 'input').pipe(
  debounceTime(1000), // Time in milliseconds between key events
  distinctUntilChanged(), // If previous query is different from current   
  map(event => event.target.value) // get value,
  filter(query => query) // if character length greater then 0,
  tap(query => console.log(`About to make an API call with query: ${query}`)),
  switchMap(getCars) // Your api call
);

search.subscribe(successCallback);

Try to use debounce() and distinctUntileChanged():

handleFilterEvent = new Subject<any>();

ngOnInit(){
    this.handleFilterEvent
        .debounceTime(500)
        .distinctUntilChanged()
        .subscribe(value => {
            this.myService.search(searchInput)
                .subscribe((res) => {
                    this.myArray.content = res;
            });
        });
}

onSearchChange(value){
    if(value) {
        this.handleFilterEvent.next(value);
    }
}

HTML:

<input type="text" class="form-control" 
   (input)="onSearchChange($event.target.value)">

You can achieve that using fromEvent and Viewchild

Just reference the ViewChild as follow:

@ViewChild('yourInput') inputName: any;

Then you can simply use:

fromEvent(this.inputName.nativeElement, 'keyup')
            .pipe(
                map((k: any) => k.target.value),
                debounceTime(1000)
            )
            .subscribe(value => {
              foo();
            });

This is true RXJS way, I have changed myArray.content to be Observable, as this way you can use piping and mapping. This will prevent multiple requests, more specifically, it will unsubscribe from the previous search before starting new one.

searchedInput$ = new Subject<string>();
myArray: { content: Observable<any> } = {};

constructor(myService: any) {
  this.myArray.content = this.searchedInput$.pipe(
    distinctUntilChanged(),
    debounceTime(400),
    switchMap(input => myService.search(input)),
  );
}

update(searchInput: string): void {
  this.searchedInput$.next(searchInput);
}

You can do the following:

In HTML:

<input type="text" #myId="ngModel" [(ngModel)]="data" />

In your ponent:

import {
  debounceTime,
  distinctUntilChanged
} from "rxjs/operators";

@ViewChild('myId', {static: true}) myControl: NgModel;

ngOnInit() {
 this.myControl
  .valueChanges // For each changes
  .pipe(
    debounceTime(400),
    distinctUntilChanged()
   )
  .subscribe(() => ...)

}

本文标签: javascriptMore elegant way to subscribe with rjxs instead of setTimeout functionStack Overflow