docs(toh-pt6): bind to a component method rather than a RxJS Subject

updated text accordingly
This commit is contained in:
Ward Bell 2016-07-19 12:26:14 -07:00
parent 72efdc5e5b
commit ce2e14e4ae
3 changed files with 34 additions and 28 deletions

View File

@ -1,7 +1,7 @@
<!-- #docregion --> <!-- #docregion -->
<div id="search-component"> <div id="search-component">
<h4>Hero Search</h4> <h4>Hero Search</h4>
<input #searchBox id="search-box" (keyup)="search.next(searchBox.value)" /> <input #searchBox id="search-box" (keyup)="search(searchBox.value)" />
<div> <div>
<div *ngFor="let hero of heroes | async" <div *ngFor="let hero of heroes | async"
(click)="gotoDetail(hero)" class="search-result" > (click)="gotoDetail(hero)" class="search-result" >

View File

@ -14,22 +14,26 @@ import { Hero } from './hero';
providers: [HeroSearchService] providers: [HeroSearchService]
}) })
export class HeroSearchComponent implements OnInit { export class HeroSearchComponent implements OnInit {
// #docregion subject
search = new Subject<string>();
// #enddocregion subject
// #docregion search // #docregion search
heroes: Observable<Hero>; heroes: Observable<Hero>;
// #enddocregion search // #enddocregion search
// #docregion searchSubject
searchSubject = new Subject<string>();
// #enddocregion searchSubject
constructor( constructor(
private heroSearchService: HeroSearchService, private heroSearchService: HeroSearchService,
private router: Router) {} private router: Router) {}
// #docregion searchSubject
// Push a search term into the observable stream.
search(term: string) { this.searchSubject.next(term); }
// #enddocregion searchSubject
// #docregion search // #docregion search
ngOnInit() { ngOnInit() {
this.heroes = this.search this.heroes = this.searchSubject
.asObservable() // "cast" as Observable .asObservable() // cast as Observable
.debounceTime(300) // wait for 300ms pause in events .debounceTime(300) // wait for 300ms pause in events
.distinctUntilChanged() // ignore if next search term is same as previous .distinctUntilChanged() // ignore if next search term is same as previous
.switchMap(term => term // switch to new observable each time .switchMap(term => term // switch to new observable each time

View File

@ -407,39 +407,37 @@ block review
The component template is simple - just a textbox and a list of matching search results. The component template is simple - just a textbox and a list of matching search results.
+makeExample('toh-6/ts/app/hero-search.component.html', null,'hero-search.component.html') +makeExample('toh-6/ts/app/hero-search.component.html', null,'hero-search.component.html')
:marked :marked
As the user types in the search box, a *keyup* event binding calls `search.next` with the new search box value. As the user types in the search box, a *keyup* event binding calls the component's `search` with the new search box value.
The component's data bound `search` property returns a `Subject`.
A `Subject` is a producer of an _observable_ event stream.
Each call to `search.next` puts a new string into this subject's _observable_ stream.
The `*ngFor` repeats *hero* objects from the component's `heroes` property. No surprise there. The `*ngFor` repeats *hero* objects from the component's `heroes` property. No surprise there.
But `heroes` is an `Observable` of heroes, not an array of heroes. But, as we'll soon see, the `heroes` property returns an `Observable` of heroes, not an array of heroes.
The `*ngFor` can't do anything with that until we flow it through the `AsyncPipe` (`heroes | async`). The `*ngFor` can't do anything with an observable until we flow it through the `AsyncPipe` (`heroes | async`).
The `AsyncPipe` subscribes to the observable and produces the array of heroes to `*ngFor`. The `AsyncPipe` subscribes to the observable and produces the array of heroes to `*ngFor`.
Time to create the `HeroSearchComponent` class and metadata. Time to create the `HeroSearchComponent` class and metadata.
+makeExample('toh-6/ts/app/hero-search.component.ts', null,'hero-search.component.ts') +makeExample('toh-6/ts/app/hero-search.component.ts', null,'hero-search.component.ts')
:marked :marked
Scroll down to where we create the `search` subject. Focus on the `searchSubject`.
+makeExample('toh-6/ts/app/hero-search.component.ts', 'subject') +makeExample('toh-6/ts/app/hero-search.component.ts', 'searchSubject')(format=".")
:marked :marked
We're binding to that `search` subject in our template. A `Subject` is a producer of an _observable_ event stream.
The user is sending it a stream of strings, the filter criteria for the name search. This `searchSubject` produces an `Observable` of strings, the filter criteria for the name search.
Each call to `search` puts a new string into this subject's _observable_ stream by calling `next`.
A `Subject` is also an `Observable`. A `Subject` is also an `Observable`.
We're going to access that `Observable` and append operators to it that turn the stream We're going to access that `Observable` and turn the stream
of strings into a stream of `Hero[]` arrays. of strings into a stream of `Hero[]` arrays, the `heroes` property.
Each user keystroke could result in a new http request returning a new Observable array of heroes.
This could be a very chatty, taxing our server resources and burning up our cellular network data plan.
Fortunately we can chain `Observable` operators to reduce the request flow
and still get timely results. Here's how:
+makeExample('toh-6/ts/app/hero-search.component.ts', 'search')(format=".") +makeExample('toh-6/ts/app/hero-search.component.ts', 'search')(format=".")
:marked :marked
If we passed every user keystroke directly to the `HeroSearchService`, we'd unleash a storm of http requests.
Bad idea. We don't want to tax our server resources and burn through our cellular network data plan.
Fortunately we can chain `Observable` operators to the string `Observable` that reduce the request flow.
We'll make fewer calls to the `HeroSearchService` and still get timely results. Here's how:
* The `asObservable` operator casts the `Subject` as an `Observable` of filter strings. * The `asObservable` operator casts the `Subject` as an `Observable` of filter strings.
* `debounceTime(300)` waits until the flow of new string events pauses for 300 milliseconds * `debounceTime(300)` waits until the flow of new string events pauses for 300 milliseconds
@ -449,7 +447,7 @@ block review
There's no point in repeating a request for the same search term. There's no point in repeating a request for the same search term.
* `switchMap` calls our search service for each search term that makes it through the `debounce` and `distinctUntilChanged` gauntlet. * `switchMap` calls our search service for each search term that makes it through the `debounce` and `distinctUntilChanged` gauntlet.
It discards previous search observables, returning only the latest search service observable. It cancels and discards previous search observables, returning only the latest search service observable.
.l-sub-section .l-sub-section
:marked :marked
@ -462,10 +460,14 @@ block review
`switchMap` preserves the original request order while returning `switchMap` preserves the original request order while returning
only the observable from the most recent http call. only the observable from the most recent http call.
Results from prior calls will be discarded. Results from prior calls are canceled and discarded.
We also short-circuit the http call and return an observable containing an empty array We also short-circuit the http call and return an observable containing an empty array
if the search text is empty. if the search text is empty.
Note that _canceling_ the `HeroSearchService` observable won't actually abort a pending http request
until the service supports that feature, a topic for another day.
We are content for now to discard unwanted results.
:marked :marked
* `catch` intercepts a failed observable. * `catch` intercepts a failed observable.
Our simple example prints the error to the console; a real life application should do better. Our simple example prints the error to the console; a real life application should do better.