使用 RxJs switchMap 发送太多请求的 Api typeahead 搜索角度

问题描述

我目前正在构建一个提前类型搜索。我正在尝试从 https://www.learnrxjs.io/learn-rxjs/recipes/type-ahead 的示例中对其进行调整,但我需要在击键时进行 http 调用。我正在针对我的 api 中的时区对其进行测试。它有效,但似乎每次输入 keyup 时都会复合 api 调用

如果我输入“我们”,它将返回结果并进行两个相同的 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;
    });
  }

apiData 服务:

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=${1}`))
    )
    .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=${1}`))
    ).subscribe(
      res => this.results = res.body.data
    );

  }

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

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