</label> <!-- (click) passes input value to add() and then clears the input --> <button (click)="add(heroName.value); heroName.value=''"> add </button> </div>
In response to a click event, call the component's click handler and then clear the input field so that it's ready for another name.
当点击事件触发时,调用组件的点击处理器,然后清空这个输入框,以便用来输入另一个名字。
add(name: string): void { name = name.trim(); if (!name) { return; } this.heroService.addHero({ name } as Hero) .subscribe(hero => { this.heroes.push(hero); }); }
When the given name is non-blank, the handler creates a Hero-like object from the name (it's only missing the id) and passes it to the services addHero() method.
当指定的名字非空时,这个处理器会用这个名字创建一个类似于 Hero 的对象(只缺少 id 属性),并把它传给服务的 addHero() 方法。
When addHero saves successfully, the subscribe callback receives the new hero and pushes it into to the heroes list for display.
To position the delete button at the far right of the hero entry, add some CSS to the heroes.component.css. You'll find that CSS in the final review code below.
Although the component delegates hero deletion to the HeroService, it remains responsible for updating its own list of heroes. The component's delete() method immediately removes the hero-to-delete from that list, anticipating that the HeroService will succeed on the server.
If you neglect to subscribe(), the service will not send the delete request to the server! As a rule, an Observabledoes nothing until something subscribes!
Confirm this for yourself by temporarily removing the subscribe(), clicking "Dashboard", then clicking "Heroes". You'll see the full list of heroes again.
In this last exercise, you learn to chain Observable operators together so you can minimize the number of similar HTTP requests and consume network bandwidth economically.
You will add a heroes search feature to the Dashboard. As the user types a name into a search box, you'll make repeated HTTP requests for heroes filtered by that name. Your goal is to issue only as many requests as necessary.
Start by adding a searchHeroes method to the HeroService.
先把 searchHeroes 方法添加到 HeroService 中。
/* 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[]>(`${this.heroesUrl}/?name=${term}`).pipe( tap(_ => this.log(`found heroes matching "${term}"`)), catchError(this.handleError<Hero[]>('searchHeroes', [])) ); }
The method returns immediately with an empty array if there is no search term. The rest of it closely resembles getHeroes(). The only significant difference is the URL, which includes a query string with the search term.
Replace the generated HeroSearchComponent class and metadata as follows.
修改所生成的 HeroSearchComponent 类及其元数据,代码如下:
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 asearch 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)), ); } }
Notice the declaration of heroes$ as an Observable
注意,heroes$ 声明为一个 Observable
heroes$: Observable<Hero[]>;
You'll set it in ngOnInit(). Before you do, focus on the definition of searchTerms.
The searchTerms property is declared as an RxJS Subject.
searchTerms 属性声明成了 RxJS 的 Subject 类型。
private searchTerms = new Subject<string>(); // Push asearch term into the observable stream. search(term: string): void { this.searchTerms.next(term); }
A Subject is both a source of observable values and an Observable itself. You can subscribe to a Subject as you would any Observable.
Every time the user types in the textbox, the binding calls search() with the textbox value, a "search term". The searchTerms becomes an Observable emitting a steady stream of search terms.
Passing a new search term directly to the searchHeroes() after every user keystroke would create an excessive amount of HTTP requests, taxing server resources and burning through the cellular network data plan.
Instead, the ngOnInit() method pipes the searchTerms observable through a sequence of RxJS operators that reduce the number of calls to the searchHeroes(), ultimately returning an observable of timely hero search results (each a Hero[]).
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)), );
debounceTime(300) waits until the flow of new string events pauses for 300 milliseconds before passing along the latest string. You'll never make requests more frequently than 300ms.
distinctUntilChanged() ensures that a request is sent only if the filter text changed.
distinctUntilChanged() 会确保只在过滤条件变化时才发送请求。
switchMap() calls the search service for each search term that makes it through debounce and distinctUntilChanged. It cancels and discards previous search observables, returning only the latest search service observable.
With the switchMap operator, every qualifying key event can trigger an HttpClient.get() method call. Even with a 300ms pause between requests, you could have multiple HTTP requests in flight and they may not return in the order sent.
switchMap() preserves the original request order while returning only the observable from the most recent HTTP method call. Results from prior calls are canceled and discarded.
Note that canceling a previous searchHeroes()Observable doesn't actually abort a pending HTTP request. Unwanted results are simply discarded before they reach your application code.
Run the app again. In the Dashboard, enter some text in the search box. If you enter characters that match any existing hero names, you'll see something like this.
You're at the end of your journey, and you've accomplished a lot.
旅程即将结束,不过你已经收获颇丰。
You added the necessary dependencies to use HTTP in the app.
你添加了在应用程序中使用 HTTP 的必备依赖。
You refactored HeroService to load heroes from a web API.
你重构了 HeroService,以通过 web API 来加载英雄数据。
You extended HeroService to support post(), put(), and delete() methods.
你扩展了 HeroService 来支持 post()、put() 和 delete() 方法。
You updated the components to allow adding, editing, and deleting of heroes.
你修改了组件,以允许用户添加、编辑和删除英雄。
You configured an in-memory web API.
你配置了一个内存 Web API。
You learned how to use observables.
你学会了如何使用“可观察对象”。
This concludes the "Tour of Heroes" tutorial. You're ready to learn more about Angular development in the fundamentals section, starting with the Architecture guide.