Api 使用 RxJs switchMap 发送太多请求的预输入搜索 (angular)

Api typeahead search using RxJs switchMap sending too many requests (angular)

我目前正在构建预输入搜索。我正在尝试从这里的示例中改编它 https://www.learnrxjs.io/learn-rxjs/recipes/type-ahead 但我需要在击键时进行 http 调用。我正在根据 api 中的时区对其进行测试。它有效,但似乎每次我输入 keyup.

时都会调用 api

如果我键入 'us',它将 return 结果并进行两次相同的 api 调用。如果我添加 'a' 进行 'usa' 它将进行 3 次 api 调用。如果我退格一个字母,它会变成 4,依此类推。

根据我的理解,switchMap 应该取消随着新呼叫进入而进行的呼叫,但我的实现似乎并没有这样做。当我继续研究这个例子时,我终究无法弄清楚为什么它会这样做。我尝试使用 .share() 来尝试合并流,但它似乎没有帮助。

搜索服务:

results: any;

search(table: string, page: any, elementId: string) {
    const searchInput = document.getElementById(elementId);
    const keyup$ = fromEvent(searchInput, 'keyup');
    let searchQuery;
    keyup$.pipe(
      debounceTime(500),
      map((e: any) => searchQuery = e.target.value),
      distinctUntilChanged(),
      switchMap(value => this.apiDataService.getSearchData(this.apiDataService.apiBase + `/search?table=${table}&query=${searchQuery}&page=${page}`))
    )
    .subscribe(res => {
      this.results = res.body.data;
    });
  }

api数据服务:

getSearchData(dataUrl: string): Observable<any> {
    return this.http.get<[]>(dataUrl, this.httpOptions);
}

html分量:

<input id="timezoneSearch" placeholder="Search" [(ngModel)]="search.params" (keyup)="search.search('timezone', '1', 'timezoneSearch')" mdbInput type="text">


<div class="dropdown-item" *ngFor="let option of search.results"
 (click)="accordion.selectOption(obj.currentObject, option.uuid, 'admin', 'ktimezone')">                              
  {{option.name}}
</div>

只需订阅一次(可能在 ngAfterViewInit() 中,因为您正在访问 dom):

 const searchInput = document.getElementById('timezoneSearch');
    const keyup$ = fromEvent(searchInput, 'keyup');
    keyup$.pipe(
      debounceTime(500),
      distinctUntilChanged(), // I do not think is actually necessary
      switchMap(value => this.apiDataService.getSearchData(this.apiDataService.apiBase + `/search?table=${'timezone'}&query=${value}&page=`))
    )
    .subscribe(res => {
      this.results = res.body.data;
    });

另一种可能性是在你的管道中添加一个 take(1),这对我来说有点奇怪,但可以让你像通过 html 那样提供参数。

您可以只订阅输入元素的输入事件:

<input #timezoneSearch placeholder="Search" [(ngModel)]="search.params" mdbInput type="text">
  @ViewChild('timezoneSearch') timezoneSearch: ElementRef;

  ngAfterViewInit() {
    fromEvent(this.timezoneSearch.nativeElement, 'input').pipe(
      debounceTime(500),
      map((e: InputEvent) => e.target.value),
      switchMap(value => this.apiDataService.getSearchData(this.apiDataService.apiBase + `/search?table=${'timezone'}&query=${value}&page=`))
    ).subscribe(
      res => this.results = res.body.data
    );

  }

此外,您不需要将结果放在字段中,而是直接使用模板中的 Observable:

<div *ngFor="let option of results$ | async"
 ...
</div>
  ngAfterViewInit() {
    this.results$ = fromEvent(this.timezoneSearch.nativeElement, 'input').pipe(
      debounceTime(500),
      map((e: InputEvent) => e.target.value),
      switchMap(value => this.apiDataService.getSearchData(this.apiDataService.apiBase + `/search?table=${'timezone'}&query=${value}&page=`)),
      map(res => res.body.data)
    );