docs: remove rxjs-extensions in favor of explict imports (#3075)

Triggered by #2620 which it closes.
This commit is contained in:
Ward Bell 2017-01-05 01:12:06 -08:00 committed by GitHub
parent 0d5877db1c
commit aa6f503331
26 changed files with 180 additions and 210 deletions

View File

@ -1,7 +1,8 @@
// #docregion // #docregion
import { Pipe, PipeTransform } from '@angular/core'; import { Pipe, PipeTransform } from '@angular/core';
import { Http } from '@angular/http'; import { Http } from '@angular/http';
import './rxjs-extensions';
import 'rxjs/add/operator/map';
// #docregion pipe-metadata // #docregion pipe-metadata
@Pipe({ @Pipe({

View File

@ -1,7 +1,10 @@
// #docregion // #docregion
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import './rxjs-extensions'; import 'rxjs/add/observable/interval';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/take';
@Component({ @Component({
selector: 'hero-message', selector: 'hero-message',

View File

@ -1,5 +0,0 @@
// Extensions to RxJS used in this app.
import 'rxjs/add/observable/interval';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/take';

View File

@ -1,12 +1,6 @@
// #docplaster
// #docregion // #docregion
import { Component } from '@angular/core'; import { Component } from '@angular/core';
// #docregion import-rxjs
// Add the RxJS Observable operators.
import './rxjs-operators';
// #enddocregion import-rxjs
@Component({ @Component({
selector: 'my-app', selector: 'my-app',
template: ` template: `
@ -17,4 +11,3 @@ import './rxjs-operators';
` `
}) })
export class AppComponent { } export class AppComponent { }
// #enddocregion

View File

@ -1,7 +1,7 @@
// #docregion // #docregion
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { HttpModule, JsonpModule } from '@angular/http'; import { HttpModule, JsonpModule } from '@angular/http';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';

View File

@ -1,8 +1,8 @@
// #docplaster // #docplaster
// #docregion // #docregion
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { HttpModule, JsonpModule } from '@angular/http'; import { HttpModule, JsonpModule } from '@angular/http';

View File

@ -1,16 +0,0 @@
// #docregion
// import 'rxjs/Rx'; // adds ALL RxJS statics & operators to Observable
// See node_module/rxjs/Rxjs.js
// Import just the rxjs statics and operators needed for THIS app.
// Statics
import 'rxjs/add/observable/throw';
// Operators
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/toPromise';

View File

@ -1,10 +1,15 @@
// #docplaster // #docplaster
// #docregion // #docregion
// Promise Version // Promise Version
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http'; import { Http, Response } from '@angular/http';
import { Headers, RequestOptions } from '@angular/http'; import { Headers, RequestOptions } from '@angular/http';
import { Hero } from './hero';
// #docregion rxjs-imports
import 'rxjs/add/operator/toPromise';
// #enddocregion rxjs-imports
import { Hero } from './hero';
@Injectable() @Injectable()
export class HeroService { export class HeroService {

View File

@ -2,16 +2,21 @@
// #docregion // #docregion
// Observable Version // Observable Version
// #docregion v1 // #docregion v1
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http'; import { Http, Response } from '@angular/http';
// #enddocregion v1 // #enddocregion v1
// #docregion import-request-options // #docregion import-request-options
import { Headers, RequestOptions } from '@angular/http'; import { Headers, RequestOptions } from '@angular/http';
// #enddocregion import-request-options // #enddocregion import-request-options
// #docregion v1 // #docregion v1
import { Hero } from './hero'; // #docregion rxjs-imports
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
// #enddocregion rxjs-imports
import { Hero } from './hero';
@Injectable() @Injectable()
export class HeroService { export class HeroService {

View File

@ -1,10 +1,16 @@
/* tslint:disable: member-ordering forin */ /* tslint:disable: member-ordering forin */
// #docplaster // #docplaster
// #docregion // #docregion
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';
// #docregion rxjs-imports
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/switchMap';
// #docregion import-subject // #docregion import-subject
import { Subject } from 'rxjs/Subject'; import { Subject } from 'rxjs/Subject';
// #enddocregion import-subject // #enddocregion import-subject
import { WikipediaService } from './wikipedia.service'; import { WikipediaService } from './wikipedia.service';
@ -12,21 +18,25 @@ import { WikipediaService } from './wikipedia.service';
@Component({ @Component({
moduleId: module.id, moduleId: module.id,
selector: 'my-wiki-smart', selector: 'my-wiki-smart',
templateUrl: './wiki.component.html', template: `
<h1>Smarter Wikipedia Demo</h1>
<p>Search when typing stops</p>
<input #term (keyup)="search(term.value)"/>
<ul>
<li *ngFor="let item of items | async">{{item}}</li>
</ul>`,
providers: [ WikipediaService ] providers: [ WikipediaService ]
}) })
export class WikiSmartComponent implements OnInit { export class WikiSmartComponent implements OnInit {
title = 'Smarter Wikipedia Demo';
fetches = 'Fetches when typing stops';
items: Observable<string[]>; items: Observable<string[]>;
constructor (private wikipediaService: WikipediaService) {}
// #docregion subject // #docregion subject
private searchTermStream = new Subject<string>(); private searchTermStream = new Subject<string>();
search(term: string) { this.searchTermStream.next(term); } search(term: string) { this.searchTermStream.next(term); }
// #enddocregion subject // #enddocregion subject
constructor (private wikipediaService: WikipediaService) {}
ngOnInit() { ngOnInit() {
// #docregion observable-operators // #docregion observable-operators
this.items = this.searchTermStream this.items = this.searchTermStream

View File

@ -1,11 +0,0 @@
<!-- #docregion -->
<h1>{{title}}</h1>
<p><i>{{fetches}}</i></p>
<!-- #docregion keyup -->
<input #term (keyup)="search(term.value)"/>
<!-- #enddocregion keyup -->
<ul>
<li *ngFor="let item of items | async">{{item}}</li>
</ul>

View File

@ -5,19 +5,22 @@ import { Observable } from 'rxjs/Observable';
import { WikipediaService } from './wikipedia.service'; import { WikipediaService } from './wikipedia.service';
@Component({ @Component({
moduleId: module.id,
selector: 'my-wiki', selector: 'my-wiki',
templateUrl: 'wiki.component.html', template: `
<h1>Wikipedia Demo</h1>
<p>Search after each keystroke</p>
<input #term (keyup)="search(term.value)"/>
<ul>
<li *ngFor="let item of items | async">{{item}}</li>
</ul>`,
providers: [ WikipediaService ] providers: [ WikipediaService ]
}) })
export class WikiComponent { export class WikiComponent {
title = 'Wikipedia Demo';
fetches = 'Fetches after each keystroke';
items: Observable<string[]>; items: Observable<string[]>;
constructor (private wikipediaService: WikipediaService) { }
search (term: string) { search (term: string) {
this.items = this.wikipediaService.search(term); this.items = this.wikipediaService.search(term);
} }
constructor (private wikipediaService: WikipediaService) { }
} }

View File

@ -3,6 +3,8 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Jsonp } from '@angular/http'; import { Jsonp } from '@angular/http';
import 'rxjs/add/operator/map';
@Injectable() @Injectable()
export class WikipediaService { export class WikipediaService {
constructor(private jsonp: Jsonp) { } constructor(private jsonp: Jsonp) { }

View File

@ -2,6 +2,8 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Jsonp, URLSearchParams } from '@angular/http'; import { Jsonp, URLSearchParams } from '@angular/http';
import 'rxjs/add/operator/map';
@Injectable() @Injectable()
export class WikipediaService { export class WikipediaService {
constructor(private jsonp: Jsonp) {} constructor(private jsonp: Jsonp) {}

View File

@ -1,5 +1,4 @@
// #docregion // #docregion
export * from './logger.service'; export * from './logger.service';
export * from './rxjs-extensions';
export * from './spinner/spinner.service'; export * from './spinner/spinner.service';
export * from './nav/nav.component'; export * from './nav/nav.component';

View File

@ -1,7 +0,0 @@
// #docregion
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/finally';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/observable/of';

View File

@ -3,7 +3,11 @@
import { OnInit } from '@angular/core'; import { OnInit } from '@angular/core';
import { Http, Response } from '@angular/http'; import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/finally';
import 'rxjs/add/operator/map';
import { Hero } from '../shared/hero.model'; import { Hero } from '../shared/hero.model';

View File

@ -1,6 +1,8 @@
// #docregion // #docregion
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Rx';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import { Hero } from './hero.model'; import { Hero } from './hero.model';

View File

@ -1,6 +1,8 @@
// #docregion // #docregion
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Rx';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import { Hero } from './hero.model'; import { Hero } from './hero.model';

View File

@ -1,6 +1,8 @@
// #docregion // #docregion
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Rx';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import { Hero } from './hero.model'; import { Hero } from './hero.model';

View File

@ -1,9 +1,5 @@
// #docplaster // #docplaster
// #docregion // #docregion
// #docregion rxjs-extensions
import './rxjs-extensions';
// #enddocregion rxjs-extensions
// #docregion v1, v2 // #docregion v1, v2
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';

View File

@ -2,9 +2,20 @@
// #docregion // #docregion
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
// #docregion rxjs-imports
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject'; import { Subject } from 'rxjs/Subject';
// Observable class extensions
import 'rxjs/add/observable/of';
// Observable operators
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
// #enddocregion rxjs-imports
import { HeroSearchService } from './hero-search.service'; import { HeroSearchService } from './hero-search.service';
import { Hero } from './hero'; import { Hero } from './hero';
@ -37,15 +48,15 @@ export class HeroSearchComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.heroes = this.searchTerms this.heroes = this.searchTerms
.debounceTime(300) // wait for 300ms pause in events .debounceTime(300) // wait 300ms after each keystroke before considering the term
.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 the term changes
// return the http search observable // return the http search observable
? this.heroSearchService.search(term) ? this.heroSearchService.search(term)
// or the observable of empty heroes if no search term // or the observable of empty heroes if there was no search term
: Observable.of<Hero[]>([])) : Observable.of<Hero[]>([]))
.catch(error => { .catch(error => {
// TODO: real error handling // TODO: add real error handling
console.log(error); console.log(error);
return Observable.of<Hero[]>([]); return Observable.of<Hero[]>([]);
}); });

View File

@ -1,7 +1,9 @@
// #docregion // #docregion
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http'; import { Http } from '@angular/http';
import { Observable } from 'rxjs';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import { Hero } from './hero'; import { Hero } from './hero';
@ -13,6 +15,6 @@ export class HeroSearchService {
search(term: string): Observable<Hero[]> { search(term: string): Observable<Hero[]> {
return this.http return this.http
.get(`app/heroes/?name=${term}`) .get(`app/heroes/?name=${term}`)
.map((r: Response) => r.json().data as Hero[]); .map(response => response.json().data as Hero[]);
} }
} }

View File

@ -1,13 +0,0 @@
// #docregion
// Observable class extensions
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/throw';
// Observable operators
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/switchMap';

View File

@ -55,12 +55,6 @@ block demos-list
The root `AppComponent` orchestrates these demos: The root `AppComponent` orchestrates these demos:
+makeExample('server-communication/ts/app/app.component.ts', null, 'app/app.component.ts') +makeExample('server-communication/ts/app/app.component.ts', null, 'app/app.component.ts')
+ifDocsFor('ts')
:marked
There is nothing remarkable here _except_ for the import of RxJS operators, which is
described [later](#rxjs).
+makeExample('server-communication/ts/app/app.component.ts', 'import-rxjs')(format='.')
.l-main-section#http-providers .l-main-section#http-providers
:marked :marked
# Providing HTTP services # Providing HTTP services
@ -196,7 +190,7 @@ a#HeroService
:marked :marked
<a id="rxjs"></a> <a id="rxjs"></a>
If you are familiar with asynchronous methods in modern JavaScript, you might expect the `get` method to return a If you are familiar with asynchronous methods in modern JavaScript, you might expect the `get` method to return a
[promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise" target="_blank" title="Promise">promise</a>.
You'd expect to chain a call to `then()` and extract the heroes. You'd expect to chain a call to `then()` and extract the heroes.
Instead you're calling a `map()` method. Instead you're calling a `map()` method.
Clearly this is not a promise. Clearly this is not a promise.
@ -207,47 +201,24 @@ a#HeroService
.l-main-section .l-main-section
:marked :marked
## RxJS library ## RxJS library
[RxJS](https://github.com/ReactiveX/RxJS) ("Reactive Extensions") is a 3rd party library, endorsed by Angular, <a href="http://reactivex.io/rxjs" target="_blank" title="RxJS Reactive Extensions">RxJS</a>
that implements the [*asynchronous observable*](https://www.youtube.com/watch?v=UHI0AzD_WfY "Rob Wormald on observables") pattern. is a third party library, endorsed by Angular, that implements the
<a href="" target="_blank" title="Video: Rob Wormald on observables"><b>asynchronous observable</b></a> pattern.
All of the Developer Guide samples have installed the RxJS npm package and loaded via `system.js` All of the Developer Guide samples have installed the RxJS npm package
because observables are used widely in Angular applications. because observables are used widely in Angular applications.
_This_ app needs it when working with the HTTP client.
The app needs it when working with the HTTP client. But you must take a critical extra step to make RxJS observables usable:
Additionally, you must take a critical extra step to make RxJS observables usable. you must import the RxJS operators individually.
### Enable RxJS operators ### Enable RxJS operators
The RxJS library is large. The RxJS library is large.
Size matters when building a production application and deploying it to mobile devices. Size matters when building a production application and deploying it to mobile devices.
You should include only necessary features. You should include only necessary features.
Accordingly, Angular exposes a stripped down version of `Observable` in the `rxjs/Observable` Each code file should add the operators it needs by importing from an RxJS library.
module that lacks most of the operators such as the `map` method you The `getHeroes` method needs the `map` and `catch` operators so it imports them like this.
called above in `getHeroes`. +makeExample('server-communication/ts/app/toh/hero.service.ts', 'rxjs-imports', 'app/app.component.ts (import rxjs)')(format=".")
It's up to you to add the operators you need.
You could add _every_ RxJS operator with a single import statement.
While that is the easiest thing to do, you'd pay a penalty in extended launch time
and application size because the full library is so big.
Since this app only uses a few operators, it's better to import each `Observable`
operator and static class method, one-by-one, for a custom *Observable*
implementation tuned
precisely to the app's requirements. Put the `import` statements in one `app/rxjs-operators.ts` file.
+makeExample('server-communication/ts/app/rxjs-operators.ts', null, 'app/rxjs-operators.ts')(format=".")
:marked
If you forget an operator, the TypeScript compiler warns that it's missing and you'll update this file.
.l-sub-section
:marked
The app doesn't need _all_ of these particular operators in the `HeroService` &mdash; just `map`, `catch` and `throw`.
The other operators are for later, in the *Wiki* example [below](#more-observables).
:marked
Finally, import `rxjs-operator` into `app.component.ts`:
+makeExample('server-communication/ts/app/app.component.ts', 'import-rxjs', 'app/app.component.ts (import rxjs)')(format=".")
:marked
Now continue to the next section to return to the `HeroService`.
.l-main-section .l-main-section
a#extract-data a#extract-data
@ -521,15 +492,13 @@ block wikipedia-jsonp+
:marked :marked
### The WikiComponent ### The WikiComponent
Now that you have a service that can query the Wikipedia API Now that you have a service that can query the Wikipedia API,
turn to the component (template and class) that takes user input and displays search results. turn your attention to the component (template and class) that takes user input and displays search results.
+makeExample('server-communication/ts/app/wiki/wiki.component.html', null, 'app/wiki/wiki.component.html')
+makeExample('server-communication/ts/app/wiki/wiki.component.ts', null, 'app/wiki/wiki.component.ts') +makeExample('server-communication/ts/app/wiki/wiki.component.ts', null, 'app/wiki/wiki.component.ts')
:marked :marked
The template presents an `<input>` element *search box* to gather search terms from the user, The template presents an `<input>` element *search box* to gather search terms from the user,
and calls a `search(term)` method after each `keyup` event. and calls a `search(term)` method after each `keyup` event.
+makeExample('server-communication/ts/app/wiki/wiki.component.html', 'keyup', 'wiki/wiki.component.html')(format='.')
:marked
The component's `search(term)` method delegates to the `WikipediaService`, which returns an The component's `search(term)` method delegates to the `WikipediaService`, which returns an
observable array of string results (`Observable<string[]>`). observable array of string results (`Observable<string[]>`).
Instead of subscribing to the observable inside the component, as in the `HeroListComponent`, Instead of subscribing to the observable inside the component, as in the `HeroListComponent`,
@ -549,7 +518,7 @@ block wikipedia-jsonp+
It is inefficient, and potentially expensive on mobile devices with limited data plans. It is inefficient, and potentially expensive on mobile devices with limited data plans.
### 1. Wait for the user to stop typing ### 1. Wait for the user to stop typing
Presently, the code calls the server after every key stroke. Presently, the code calls the server after every keystroke.
It should only make requests when the user *stops typing* . It should only make requests when the user *stops typing* .
Here's how it will work after refactoring: Here's how it will work after refactoring:
figure.image-display figure.image-display
@ -569,67 +538,71 @@ block wikipedia-jsonp+
The application issues two search requests, one for *angular* and one for *http*. The application issues two search requests, one for *angular* and one for *http*.
Which response arrives first? It's unpredictable. Which response arrives first? It's unpredictable.
A load balancer could dispatch the requests to two different servers with different response times.
The results from the first *angular* request might arrive after the later *http* results.
The user will be confused if the *angular* results display to the *http* query.
When there are multiple requests in-flight, the app should present the responses When there are multiple requests in-flight, the app should present the responses
in the original request order. That won't happen if *angular* results arrive last. in the original request order.
In this example, the app must always display the results for the *http* search
no matter which response arrives first.
<a id="more-observables"></a> <a id="more-observables"></a>
## More fun with observables ## More fun with observables
You can address these problems and improve the app with the help of some nifty observable operators.
You could make changes to the `WikipediaService`, but for a better You could make changes to the `WikipediaService`, but for a better
user experience, create a copy of the `WikiComponent` instead and make it smarter. user experience, create a copy of the `WikiComponent` instead and make it smarter,
Here's the `WikiSmartComponent` which uses the same template. with the help of some nifty observable operators.
+makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', null, 'app/wiki/wiki-smart.component.ts') Here's the `WikiSmartComponent`, shown next to the original `WikiComponent`
+makeTabs(
`server-communication/ts/app/wiki/wiki-smart.component.ts,
server-communication/ts/app/wiki/wiki.component.ts`,
null,
`app/wiki/wiki-smart.component.ts,
app/wiki/wiki.component.ts`
)
:marked :marked
While the templates are virtually identical,
there's a lot more RxJS in the "smart" version,
starting with `debounceTime`, `distinctUntilChanged`, and `switchMap` operators,
imported as [described above](#rxjs).
### Create a stream of search terms ### Create a stream of search terms
The template still binds to the search box `keyup` event and passes the complete search box value The `WikiComponent` passes a new search term directly to the `WikipediaService` after every keystroke.
into the component's `search` method after every user keystroke.
+makeExample('server-communication/ts/app/wiki/wiki.component.html', 'keyup', 'app/wiki/wiki.component.html (input)')(format='.') The `WikiSmartComponent` class turns the user's keystrokes into an observable _stream of search terms_
:marked with the help of a `Subject`, which you import from RxJS:
The `WikiSmartComponent` turns the search box values into an observable _stream of search terms_ +makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'import-subject')(format='.')
with the help of a `Subject` which you import from the RxJS observable library:
+makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'import-subject', 'app/wiki/wiki-smart.component.ts')
:marked :marked
The component creates a `searchTermStream` as a `Subject` of type `string`. The component creates a `searchTermStream` as a `Subject` of type `string`.
The `search` method adds each new search box value to that stream via the subject's `next` method. The `search` method adds each new search box value to that stream via the subject's `next` method.
+makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'subject', 'app/wiki/wiki-smart.component.ts')(format='.') +makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'subject')(format='.')
:marked :marked
### Listen for search terms ### Listen for search terms
Earlier, you passed each search term directly to the service and bound the template to the service results. The `WikiSmartComponent` listens to the *stream of search terms* and
processes that stream _before_ calling the service.
Now you listen to the *stream of search terms*, manipulating the stream before it reaches the `WikipediaService`. +makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'observable-operators')(format='.')
+makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'observable-operators',
'app/wiki/wiki-smart.component.ts')(format='.')
:marked :marked
Wait for the user to stop typing for at least 300 milliseconds * <a href="https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/debounce.md" target="_blank" title="debounce operator"><i>debounceTime</i></a>
([_debounceTime_](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/debounce.md)). waits for the user to stop typing for at least 300 milliseconds.
Only changed search values make it through to the service
([_distinctUntilChanged_](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/distinctuntilchanged.md)).
The `WikipediaService` returns a separate observable of string arrays (`Observable<string[]>`) for each request. * <a href="https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/distinctuntilchanged.md" target="_blank" title="distinctUntilChanged operator"><i>distinctUntilChanged</i></a>
There could be multiple requests *in-flight*, all awaiting the server's reply, ensures that the service is called only when the new search term is different from the previous search term.
which means multiple *observables-of-strings* could arrive at any moment in any order.
The [_switchMap_](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/flatmaplatest.md) * The <a href="https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/flatmaplatest.md" target="_blank" title="switchMap operator"><i>switchMap</i></a>
(formerly known as `flatMapLatest`) returns a new observable that combines these `WikipediaService` observables, calls the `WikipediaService` with a fresh, debounced search term and coordinates the stream(s) of service response.
The role of `switchMap` is particularly important.
The `WikipediaService` returns a separate observable of string arrays (`Observable<string[]>`) for each search request.
The user could issue multiple requests before a slow server has had time to reply,
which means a backlog of response observables could arrive at the client, at any moment, in any order.
The `switchMap` returns its own observable that _combines_ all `WikipediaService` response observables,
re-arranges them in their original request order, re-arranges them in their original request order,
and delivers to subscribers only the most recent search results. and delivers to subscribers only the most recent search results.
The displayed list of search results stays in sync with the user's sequence of search terms.
.l-sub-section
:marked
You added the `debounceTime`, `distinctUntilChanged`, and `switchMap` operators to the RxJS `Observable` class
in `rxjs-operators` as [described above](#rxjs).
a#xsrf a#xsrf
.l-main-section .l-main-section
:marked :marked

View File

@ -168,6 +168,10 @@ block get-heroes-details
+makeExcerpt('app/hero.service.ts', 'rxjs', '') +makeExcerpt('app/hero.service.ts', 'rxjs', '')
.l-sub-section
:marked
You'll add more operators, and learn why you must do so, [later in this tutorial](#rxjs-imports).
:marked :marked
### Extracting the data in the *then* callback ### Extracting the data in the *then* callback
@ -377,8 +381,9 @@ block observables-section-intro
### Background ### Background
An *observable* is a stream of events that we can process with array-like operators. An *observable* is a stream of events that we can process with array-like operators.
Angular core has basic support for observables. We developers augment that support with Angular core has basic support for observables.
operators and extensions from the [RxJS Observables](http://reactivex.io/rxjs/) library. We developers augment that support with operators and extensions from the
<a href="http://reactivex.io/rxjs" target="_blank" title="RxJS">RxJS library</a>.
We'll see how shortly. We'll see how shortly.
Recall that our `HeroService` quickly chained the `toPromise` operator to the `Observable` result of `http.get`. Recall that our `HeroService` quickly chained the `toPromise` operator to the `Observable` result of `http.get`.
@ -406,8 +411,15 @@ block observables-section-intro
:marked :marked
The `!{_priv}http.get()` call in `HeroSearchService` is similar to the one The `!{_priv}http.get()` call in `HeroSearchService` is similar to the one
in the `HeroService`, although the URL now has a query string. in the `HeroService`, although the URL now has a query string.
<span if-docs="ts">Another notable difference: we no longer call `toPromise`,
we simply return the *observable* instead.</span> <span if-docs="ts">A more important difference: we no longer call `toPromise`.
Instead we return the *observable* from the the `htttp.get`,
after chaining it to another RxJS operator, <code>map</code>,
to extract heroes from the response data.
RxJS operator chaining makes response processing easy and readable.
See the [discuss below about operators](#rxjs-imports).
</span>
### HeroSearchComponent ### HeroSearchComponent
@ -498,26 +510,24 @@ block observable-transformers
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.
Then we return an observable containing an empty array to clear the search result. Then we return an observable containing an empty array to clear the search result.
a#rxjs-imports
:marked
### Import RxJS operators ### Import RxJS operators
The RxJS operators are not available in Angular's base `Observable` implementation. Most RxJS operators are not included in Angular's base `Observable` implementation.
We have to extend `Observable` by *importing* them. The base implementation includes only what Angular itself requires.
We could extend `Observable` with just the operators we need here by If we want more RxJS features, we have to extend `Observable` by *importing* the libraries in which they are defined.
including the pertinent `import` statements at the top of this file. Here are all the RxJS imports _this_ component needs:
.l-sub-section +makeExample('app/hero-search.component.ts','rxjs-imports','app/hero-search.component.ts (rxjs imports)')(format='.')
:marked
Many authorities say we should do just that.
:marked
We take a different approach in this example.
We combine all of the RxJS `Observable` extensions that _our entire app_ requires into a single RxJS imports file.
+makeExample('app/rxjs-extensions.ts')(format='.')
:marked :marked
We load them all at once by importing `rxjs-extensions` at the top of `AppModule`. The `import 'rxjs/add/...'` syntax may be unfamiliar.
It's missing the usual list of symbols between the braces: `{...}`.
+makeExcerpt('app/app.module.ts', 'rxjs-extensions')(format='.')
We don't need the operator symbols themselves.
In each case, the mere act of importing the library
loads and executes the library's script file which, in turn, adds the operator to the `Observable` class.
:marked :marked
### Add the search component to the dashboard ### Add the search component to the dashboard
@ -570,7 +580,6 @@ block filetree
.file hero-search.component.css (new) .file hero-search.component.css (new)
.file hero-search.component.ts (new) .file hero-search.component.ts (new)
.file hero-search.service.ts (new) .file hero-search.service.ts (new)
.file rxjs-extensions.ts
.file hero.service.ts .file hero.service.ts
.file heroes.component.css .file heroes.component.css
.file heroes.component.html .file heroes.component.html
@ -625,14 +634,12 @@ block file-summary
`toh-6/ts/app/hero-search.service.ts, `toh-6/ts/app/hero-search.service.ts,
toh-6/ts/app/hero-search.component.ts, toh-6/ts/app/hero-search.component.ts,
toh-6/ts/app/hero-search.component.html, toh-6/ts/app/hero-search.component.html,
toh-6/ts/app/hero-search.component.css, toh-6/ts/app/hero-search.component.css`,
toh-6/ts/app/rxjs-extensions.ts`,
null, null,
`hero-search.service.ts, `hero-search.service.ts,
hero-search.component.ts, hero-search.component.ts,
hero-search.component.html, hero-search.component.html,
hero-search.component.css, hero-search.component.css`
rxjs-extensions.ts`
) )
.l-sub-section .l-sub-section