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