2018-05-15

作者: 哪种生活可以永远很轻松 | 来源:发表于2018-05-15 17:14 被阅读24次

    根据名字搜索

    https://angular.io/tutorial/toh-pt6#search-by-name

    在最后一次练习中,你要学到把 Observable 的操作符串在一起,让你能将相似 HTTP 请求的数量最小化,并节省网络带宽。

    你将往仪表盘中加入英雄搜索特性。 当用户在搜索框中输入名字时,你会不断发送根据名字过滤英雄的 HTTP 请求。 你的目标是仅仅发出尽可能少的必要请求。

    HeroService.searchHeroes

    先把 searchHeroes 方法添加到 HeroService 中。

    <code-example path="toh-pt6/src/app/hero.service.ts" region="searchHeroes" ng-version="5.2.0" style="clear: both; display: block; background-color: rgba(242, 242, 242, 0.2); border: 0.5px solid rgb(219, 219, 219); border-radius: 5px; color: rgb(51, 51, 51); margin: 16px auto; font-family: Roboto, "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><header class="ng-star-inserted" style="background-color: rgb(30, 136, 229); border-radius: 5px 5px 0px 0px; color: rgb(250, 250, 250); font-size: 16px; padding: 8px 16px;">src/app/hero.service.ts</header>

    <aio-code class="headed-code"><pre class="prettyprint lang-" style="display: flex; min-height: 32px; margin: 16px 24px; white-space: pre-wrap; -webkit-box-align: center; align-items: center; position: relative;">content_copy /* GET heroes whose name contains search term */ searchHeroes(term: string): Observable<Hero[]> { if (!term.trim()) { // if not search term, return empty hero array. return of([]); } return this.http.get<Hero[]>(api/heroes/?name=${term}).pipe( tap(_ => this.log(found heroes matching "${term}")), catchError(this.handleError<Hero[]>('searchHeroes', [])) ); }</pre></aio-code></code-example>

    如果没有搜索词,该方法立即返回一个空数组。 剩下的部分和 getHeroes() 很像。 唯一的不同点是 URL,它包含了一个由搜索词组成的查询字符串。

    为仪表盘添加搜索功能

    打开 DashboardComponent模板并且把用于搜索英雄的元素 <app-hero-search> 添加到 DashboardComponent模板的底部。

    <code-example path="toh-pt6/src/app/dashboard/dashboard.component.html" linenums="false" ng-version="5.2.0" style="clear: both; display: block; background-color: rgba(242, 242, 242, 0.2); border: 0.5px solid rgb(219, 219, 219); border-radius: 5px; color: rgb(51, 51, 51); margin: 16px auto; font-family: Roboto, "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><header class="ng-star-inserted" style="background-color: rgb(30, 136, 229); border-radius: 5px 5px 0px 0px; color: rgb(250, 250, 250); font-size: 16px; padding: 8px 16px;">src/app/dashboard/dashboard.component.html</header>

    <aio-code class="headed-code"><pre class="prettyprint lang-" style="display: flex; min-height: 32px; margin: 16px 24px; white-space: pre-wrap; -webkit-box-align: center; align-items: center; position: relative;">content_copy <h3>Top Heroes</h3> <div class="grid grid-pad"> <[a](https://angular.cn/api/router/RouterLinkWithHref) *[ngFor](https://angular.cn/api/common/NgForOf)="let hero of heroes" class="col-1-4" [routerLink](https://angular.cn/api/router/RouterLink)="/detail/{{hero.id}}"> <div class="module hero"> <h4>{{hero.name}}</h4> </div> </[a](https://angular.cn/api/router/RouterLinkWithHref)> </div> <app-hero-search></app-hero-search></pre></aio-code></code-example>

    这个模板看起来很像 HeroesComponent 模板中的 *[ngFor](https://angular.cn/api/common/NgForOf) 复写器。

    很不幸,添加这个元素让本应用挂了。 Angular 找不到哪个组件的选择器能匹配上 <app-hero-search>

    HeroSearchComponent 还不存在,这就解决。

    创建 HeroSearchComponent

    使用 CLI 创建一个 HeroSearchComponent

    <code-example language="sh" class="code-shell" ng-version="5.2.0" style="clear: both; display: block; background-color: rgb(51, 51, 51); border: 0.5px solid rgb(219, 219, 219); border-radius: 5px; color: rgb(51, 51, 51); margin: 16px auto; font-family: Roboto, "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><aio-code class="simple-code"><pre class="prettyprint lang-sh" style="display: flex; min-height: 32px; margin: 16px 24px; white-space: pre-wrap; -webkit-box-align: center; align-items: center; position: relative;">content_copy ng generate component hero-search</pre></aio-code></code-example>

    CLI 生成了 HeroSearchComponent 的三个文件,并把该组件添加到了 AppModule 的声明中。

    把生成的 HeroSearchComponent模板改成一个输入框和一个匹配到的搜索结果的列表。代码如下:

    <code-example path="toh-pt6/src/app/hero-search/hero-search.component.html" ng-version="5.2.0" style="clear: both; display: block; background-color: rgba(242, 242, 242, 0.2); border: 0.5px solid rgb(219, 219, 219); border-radius: 5px; color: rgb(51, 51, 51); margin: 16px auto; font-family: Roboto, "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><header class="ng-star-inserted" style="background-color: rgb(30, 136, 229); border-radius: 5px 5px 0px 0px; color: rgb(250, 250, 250); font-size: 16px; padding: 8px 16px;">src/app/hero-search/hero-search.component.html</header>

    <aio-code class="headed-code"><pre class="prettyprint lang-" style="display: flex; min-height: 32px; margin: 16px 24px; white-space: pre-wrap; -webkit-box-align: center; align-items: center; position: relative;">content_copy <div id="search-component"> <h4>Hero Search</h4> <input #searchBox id="search-box" (keyup)="search(searchBox.value)" /> <ul class="search-result"> <li *[ngFor](https://angular.cn/api/common/NgForOf)="let hero of heroes$ | [async](https://angular.cn/api/core/testing/async)" > <[a](https://angular.cn/api/router/RouterLinkWithHref) [routerLink](https://angular.cn/api/router/RouterLink)="/detail/{{hero.id}}"> {{hero.name}} </[a](https://angular.cn/api/router/RouterLinkWithHref)> </li> </ul> </div></pre></aio-code></code-example>

    从下面的 最终代码 中把私有 CSS 样式添加到 hero-search.component.css 中。

    当用户在搜索框中输入时,一个 keyup 事件绑定会调用该组件的 search() 方法,并传入新的搜索框的值。

    AsyncPipe

    如你所愿,*[ngFor](https://angular.cn/api/common/NgForOf) 重复渲染出了这些英雄。

    仔细看,你会发现 *[ngFor](https://angular.cn/api/common/NgForOf) 是在一个名叫 heroes$ 的列表上迭代,而不是 heroes

    <code-example path="toh-pt6/src/app/hero-search/hero-search.component.html" region="async" ng-version="5.2.0" style="clear: both; display: block; background-color: rgba(242, 242, 242, 0.2); border: 0.5px solid rgb(219, 219, 219); border-radius: 5px; color: rgb(51, 51, 51); margin: 16px auto; font-family: Roboto, "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><aio-code class="simple-code"><pre class="prettyprint lang-" style="display: flex; min-height: 32px; margin: 16px 24px; white-space: pre-wrap; -webkit-box-align: center; align-items: center; position: relative;">content_copy <li *[ngFor](https://angular.cn/api/common/NgForOf)="let hero of heroes$ | [async](https://angular.cn/api/core/testing/async)" ></pre></aio-code></code-example>

    $ 是一个命名惯例,用来表明 heroes$ 是一个 Observable,而不是数组。

    *[ngFor](https://angular.cn/api/common/NgForOf) 不能直接使用 Observable。 不过,它后面还有一个管道字符(|),后面紧跟着一个 [async](https://angular.cn/api/core/testing/async),它表示 Angular 的 [AsyncPipe](https://angular.cn/api/common/AsyncPipe)

    [AsyncPipe](https://angular.cn/api/common/AsyncPipe) 会自动订阅到 Observable,这样你就不用再在组件类中订阅了。

    修正 HeroSearchComponent

    修改所生成的 HeroSearchComponent 类及其元数据,代码如下:

    <code-example path="toh-pt6/src/app/hero-search/hero-search.component.ts" ng-version="5.2.0" style="clear: both; display: block; background-color: rgba(242, 242, 242, 0.2); border: 0.5px solid rgb(219, 219, 219); border-radius: 5px; color: rgb(51, 51, 51); margin: 16px auto; font-family: Roboto, "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><header class="ng-star-inserted" style="background-color: rgb(30, 136, 229); border-radius: 5px 5px 0px 0px; color: rgb(250, 250, 250); font-size: 16px; padding: 8px 16px;">src/app/hero-search/hero-search.component.ts</header>

    <aio-code class="headed-code"><pre class="prettyprint lang-" style="display: flex; min-height: 32px; margin: 16px 24px; white-space: pre-wrap; -webkit-box-align: center; align-items: center; position: relative;">content_copy import { [Component](https://angular.cn/api/core/Component), [OnInit](https://angular.cn/api/core/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](https://angular.cn/api/core/Component)({ selector: 'app-hero-search', templateUrl: './hero-search.component.html', styleUrls: [ './hero-search.component.css' ] }) export class HeroSearchComponent implements [OnInit](https://angular.cn/api/core/OnInit) { heroes$: Observable<Hero[]>; private searchTerms = new Subject<string>(); constructor(private heroService: HeroService) {} // Push [a](https://angular.cn/api/router/RouterLinkWithHref) 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)), ); } }</pre></aio-code></code-example>

    注意,heroes$ 声明为一个 Observable

    <code-example path="toh-pt6/src/app/hero-search/hero-search.component.ts" region="heroes-stream" ng-version="5.2.0" style="clear: both; display: block; background-color: rgba(242, 242, 242, 0.2); border: 0.5px solid rgb(219, 219, 219); border-radius: 5px; color: rgb(51, 51, 51); margin: 16px auto; font-family: Roboto, "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><aio-code class="simple-code"><pre class="prettyprint lang-" style="display: flex; min-height: 32px; margin: 16px 24px; white-space: pre-wrap; -webkit-box-align: center; align-items: center; position: relative;">content_copy heroes$: Observable<Hero[]>;</pre></aio-code></code-example>

    你将会在 ngOnInit() 中设置它,在此之前,先仔细看看 searchTerms 的定义。

    RxJS Subject 类型的 searchTerms

    searchTerms 属性声明成了 RxJS 的 Subject 类型。

    <code-example path="toh-pt6/src/app/hero-search/hero-search.component.ts" region="searchTerms" ng-version="5.2.0" style="clear: both; display: block; background-color: rgba(242, 242, 242, 0.2); border: 0.5px solid rgb(219, 219, 219); border-radius: 5px; color: rgb(51, 51, 51); margin: 16px auto; font-family: Roboto, "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><aio-code class="simple-code"><pre class="prettyprint lang-" style="display: flex; min-height: 32px; margin: 16px 24px; white-space: pre-wrap; -webkit-box-align: center; align-items: center; position: relative;">content_copy private searchTerms = new Subject<string>(); // Push [a](https://angular.cn/api/router/RouterLinkWithHref) search term into the observable stream. search(term: string): void { this.searchTerms.next(term); }</pre></aio-code></code-example>

    Subject 既是可观察对象的数据源,本身也是 Observable。 你可以像订阅任何 Observable 一样订阅 Subject

    你还可以通过调用它的 next(value) 方法往 Observable 中推送一些值,就像 search() 方法中一样。

    search() 是通过对文本框的 keystroke 事件的事件绑定来调用的。

    <code-example path="toh-pt6/src/app/hero-search/hero-search.component.html" region="input" ng-version="5.2.0" style="clear: both; display: block; background-color: rgba(242, 242, 242, 0.2); border: 0.5px solid rgb(219, 219, 219); border-radius: 5px; color: rgb(51, 51, 51); margin: 16px auto; font-family: Roboto, "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><aio-code class="simple-code"><pre class="prettyprint lang-" style="display: flex; min-height: 32px; margin: 16px 24px; white-space: pre-wrap; -webkit-box-align: center; align-items: center; position: relative;">content_copy <input #searchBox id="search-box" (keyup)="search(searchBox.value)" /></pre></aio-code></code-example>

    每当用户在文本框中输入时,这个事件绑定就会使用文本框的值(搜索词)调用 search() 函数。searchTerms 变成了一个能发出搜索词的稳定的流。

    串联 RxJS 操作符

    如果每当用户击键后就直接调用 searchHeroes() 将导致创建海量的 HTTP 请求,浪费服务器资源并消耗大量网络流量。

    应该怎么做呢?ngOnInit()searchTerms 这个可观察对象的处理管道中加入了一系列 RxJS 操作符,用以缩减对 searchHeroes() 的调用次数,并最终返回一个可及时给出英雄搜索结果的可观察对象(每次都是 Hero[] )。

    代码如下:

    <code-example path="toh-pt6/src/app/hero-search/hero-search.component.ts" region="search" ng-version="5.2.0" style="clear: both; display: block; background-color: rgba(242, 242, 242, 0.2); border: 0.5px solid rgb(219, 219, 219); border-radius: 5px; color: rgb(51, 51, 51); margin: 16px auto; font-family: Roboto, "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><aio-code class="simple-code"><pre class="prettyprint lang-" style="display: flex; min-height: 32px; margin: 16px 24px; white-space: pre-wrap; -webkit-box-align: center; align-items: center; position: relative;">content_copy 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)), );</pre></aio-code></code-example>

    • 在传出最终字符串之前,debounceTime(300) 将会等待,直到新增字符串的事件暂停了 300 毫秒。 你实际发起请求的间隔永远不会小于 300ms。

    • distinctUntilChanged 会确保只在过滤条件变化时才发送请求。

    • switchMap() 会为每个从 debouncedistinctUntilChanged 中通过的搜索词调用搜索服务。 它会取消并丢弃以前的搜索可观察对象,只保留最近的。

    借助 switchMap 操作符, 每个有效的击键事件都会触发一次 [HttpClient](https://angular.cn/api/common/http/HttpClient).get() 方法调用。 即使在每个请求之间都有至少 300ms 的间隔,仍然可能会同时存在多个尚未返回的 HTTP 请求。

    switchMap() 会记住原始的请求顺序,只会返回最近一次 HTTP 方法调用的结果。 以前的那些请求都会被取消和舍弃。

    注意,取消前一个 searchHeroes() 可观察对象并不会中止尚未完成的 HTTP 请求。 那些不想要的结果只会在它们抵达应用代码之前被舍弃。

    记住,组件类中并没有订阅 heroes$ 这个可观察对象,而是由模板中的 AsyncPipe 完成的。

    试试看

    再次运行本应用。在这个 仪表盘 中,在搜索框中输入一些文字。如果你输入的字符匹配上了任何现有英雄的名字,你将会看到如下效果:

    <figure style="background: rgb(255, 255, 255); padding: 20px; display: inline-block; box-shadow: rgba(0, 0, 0, 0.2) 2px 2px 5px 0px; margin: 0px 0px 14px; border-radius: 4px; color: rgba(0, 0, 0, 0.87); font-family: Roboto, "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> Hero Search Component

    </figure>

    这里出现了 | async

    相关文章

      网友评论

        本文标题:2018-05-15

        本文链接:https://www.haomeiwen.com/subject/zjdydftx.html