1 创建搜索组件 HeroSearchComponent
ng generate component hero-search
2 添加搜索组件模板
- 需要一个搜索框,用来记录用户输入的值
<input #searchBox id="search-box" (input)="search(searchBox.value)" />
- 需要一个显示列表,用来呈现搜索结果给用户
<li *ngFor="let hero of heroes$ | async" >
- 请注意这里是
heroes$ | async
, *ngFor只会遍历数据,而heroes$
是个Observable
而不是数组. 所以这里才会需要| async
管道后面的async,用来自动订阅 Observable
src/app/hero-search/hero-search.component.html
<div id="search-component">
<h4><label for="search-box">Hero Search</label></h4>
<input #searchBox id="search-box" (input)="search(searchBox.value)" />
<ul class="search-result">
<li *ngFor="let hero of heroes$ | async" >
<a routerLink="/detail/{{hero.id}}">
{{hero.name}}
</a>
</li>
</ul>
</div>
3 修改搜索组件类
src/app/hero-search/hero-search.component.ts
import { Component, OnInit } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import {
debounceTime, distinctUntilChanged, switchMap
} from 'rxjs/operators';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';
@Component({
selector: 'app-hero-search',
templateUrl: './hero-search.component.html',
styleUrls: [ './hero-search.component.css' ]
})
export class HeroSearchComponent implements OnInit {
heroes$: Observable<Hero[]>;
private searchTerms = new Subject<string>();
constructor(private heroService: HeroService) {}
// Push a search term into the observable stream.
search(term: string): void {
this.searchTerms.next(term);
}
ngOnInit(): void {
this.heroes$ = this.searchTerms.pipe(
// wait 300ms after each keystroke before considering the term
debounceTime(300),
// ignore new term if same as previous term
distinctUntilChanged(),
// switch to new search observable each time the term changes
switchMap((term: string) => this.heroService.searchHeroes(term)),
);
}
}
- 3.1 请注意这里
heroes$
声明为一个 Observable
heroes$: Observable<Hero[]>;
- 3.2 Subject 既是可观察对象的数据源,本身也是 Observable。 你可以像订阅任何 Observable 一样订阅 Subject。
private searchTerms = new Subject<string>();
// Push a search term into the observable stream.
search(term: string): void {
this.searchTerms.next(term);
}
- 3.3 搜索页面中将调用 search方法,每当用户在文本框中输入时,这个事件绑定就会使用文本框的值(搜索词)调用 search() 函数。 searchTerms 变成了一个能发出搜索词的稳定的流。
<input #searchBox id="search-box" (input)="search(searchBox.value)" />
4 搜索的最佳实践 Best Practice
this.heroes$ = this.searchTerms.pipe(
// wait 300ms after each keystroke before considering the term
debounceTime(300),
// ignore new term if same as previous term
distinctUntilChanged(),
// switch to new search observable each time the term changes
switchMap((term: string) => this.heroService.searchHeroes(term)),
);
- 4.2 如果每当用户击键后就直接调用 searchHeroes() 将导致创建海量的 HTTP 请求,浪费服务器资源并干扰数据调度计划。那该如何做呢?
在传出最终字符串之前,debounceTime(300) 将会等待,直到新增字符串的事件暂停了 300 毫秒。 你实际发起请求的间隔永远不会小于 300ms。
// wait 300ms after each keystroke before considering the term
debounceTime(300),
- 4.3 性能优化,如果新的输入
term
和原来的一样, 则不会发送请求,因为结果也一样
// ignore new term if same as previous term
distinctUntilChanged(),
- 4.4
switchMap()
会为每个从 debounce()
和 distinctUntilChanged()
中通过的搜索词调用搜索服务。 它会取消并丢弃以前的搜索可观察对象,只保留最近的。
// switch to new search observable each time the term changes
switchMap((term: string) => this.heroService.searchHeroes(term)),
5 看下实际效果