docs: remove rxjs-extensions in favor of explict imports (#3075)
Triggered by #2620 which it closes.
This commit is contained in:
parent
0d5877db1c
commit
aa6f503331
|
@ -1,7 +1,8 @@
|
|||
// #docregion
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { Http } from '@angular/http';
|
||||
import './rxjs-extensions';
|
||||
|
||||
import 'rxjs/add/operator/map';
|
||||
|
||||
// #docregion pipe-metadata
|
||||
@Pipe({
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import './rxjs-extensions';
|
||||
import 'rxjs/add/observable/interval';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/operator/take';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-message',
|
||||
|
|
|
@ -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';
|
|
@ -1,12 +1,6 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
// #docregion import-rxjs
|
||||
// Add the RxJS Observable operators.
|
||||
import './rxjs-operators';
|
||||
// #enddocregion import-rxjs
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
template: `
|
||||
|
@ -17,4 +11,3 @@ import './rxjs-operators';
|
|||
`
|
||||
})
|
||||
export class AppComponent { }
|
||||
// #enddocregion
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// #docregion
|
||||
import { NgModule } from '@angular/core';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { HttpModule, JsonpModule } from '@angular/http';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
import { NgModule } from '@angular/core';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { HttpModule, JsonpModule } from '@angular/http';
|
||||
|
||||
|
||||
|
|
|
@ -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';
|
|
@ -1,10 +1,15 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
// Promise Version
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Http, Response } from '@angular/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Http, Response } 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()
|
||||
export class HeroService {
|
||||
|
|
|
@ -2,16 +2,21 @@
|
|||
// #docregion
|
||||
// Observable Version
|
||||
// #docregion v1
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Http, Response } from '@angular/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Http, Response } from '@angular/http';
|
||||
// #enddocregion v1
|
||||
// #docregion import-request-options
|
||||
import { Headers, RequestOptions } from '@angular/http';
|
||||
// #enddocregion import-request-options
|
||||
// #docregion v1
|
||||
|
||||
import { Hero } from './hero';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
// #docregion rxjs-imports
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/operator/catch';
|
||||
import 'rxjs/add/operator/map';
|
||||
// #enddocregion rxjs-imports
|
||||
|
||||
import { Hero } from './hero';
|
||||
|
||||
@Injectable()
|
||||
export class HeroService {
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
/* tslint:disable: member-ordering forin */
|
||||
// #docplaster
|
||||
// #docregion
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
// #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
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
// #enddocregion import-subject
|
||||
|
||||
import { WikipediaService } from './wikipedia.service';
|
||||
|
@ -12,21 +18,25 @@ import { WikipediaService } from './wikipedia.service';
|
|||
@Component({
|
||||
moduleId: module.id,
|
||||
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 ]
|
||||
})
|
||||
export class WikiSmartComponent implements OnInit {
|
||||
title = 'Smarter Wikipedia Demo';
|
||||
fetches = 'Fetches when typing stops';
|
||||
items: Observable<string[]>;
|
||||
|
||||
constructor (private wikipediaService: WikipediaService) {}
|
||||
|
||||
// #docregion subject
|
||||
private searchTermStream = new Subject<string>();
|
||||
search(term: string) { this.searchTermStream.next(term); }
|
||||
// #enddocregion subject
|
||||
|
||||
constructor (private wikipediaService: WikipediaService) {}
|
||||
|
||||
ngOnInit() {
|
||||
// #docregion observable-operators
|
||||
this.items = this.searchTermStream
|
||||
|
|
|
@ -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>
|
|
@ -5,19 +5,22 @@ import { Observable } from 'rxjs/Observable';
|
|||
import { WikipediaService } from './wikipedia.service';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
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 ]
|
||||
})
|
||||
export class WikiComponent {
|
||||
title = 'Wikipedia Demo';
|
||||
fetches = 'Fetches after each keystroke';
|
||||
items: Observable<string[]>;
|
||||
|
||||
constructor (private wikipediaService: WikipediaService) { }
|
||||
|
||||
search (term: string) {
|
||||
this.items = this.wikipediaService.search(term);
|
||||
}
|
||||
|
||||
constructor (private wikipediaService: WikipediaService) { }
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { Jsonp } from '@angular/http';
|
||||
|
||||
import 'rxjs/add/operator/map';
|
||||
|
||||
@Injectable()
|
||||
export class WikipediaService {
|
||||
constructor(private jsonp: Jsonp) { }
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { Jsonp, URLSearchParams } from '@angular/http';
|
||||
|
||||
import 'rxjs/add/operator/map';
|
||||
|
||||
@Injectable()
|
||||
export class WikipediaService {
|
||||
constructor(private jsonp: Jsonp) {}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
// #docregion
|
||||
export * from './logger.service';
|
||||
export * from './rxjs-extensions';
|
||||
export * from './spinner/spinner.service';
|
||||
export * from './nav/nav.component';
|
||||
|
|
|
@ -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';
|
|
@ -3,7 +3,11 @@
|
|||
|
||||
import { OnInit } from '@angular/core';
|
||||
import { Http, Response } from '@angular/http';
|
||||
|
||||
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';
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
// #docregion
|
||||
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';
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
// #docregion
|
||||
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';
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
// #docregion
|
||||
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';
|
||||
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
// #docregion rxjs-extensions
|
||||
import './rxjs-extensions';
|
||||
// #enddocregion rxjs-extensions
|
||||
|
||||
// #docregion v1, v2
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
|
|
@ -2,9 +2,20 @@
|
|||
// #docregion
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
// #docregion rxjs-imports
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
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 { Hero } from './hero';
|
||||
|
||||
|
@ -37,15 +48,15 @@ export class HeroSearchComponent implements OnInit {
|
|||
|
||||
ngOnInit(): void {
|
||||
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
|
||||
.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
|
||||
? 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[]>([]))
|
||||
.catch(error => {
|
||||
// TODO: real error handling
|
||||
// TODO: add real error handling
|
||||
console.log(error);
|
||||
return Observable.of<Hero[]>([]);
|
||||
});
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
// #docregion
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Http, Response } from '@angular/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Http } from '@angular/http';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/operator/map';
|
||||
|
||||
import { Hero } from './hero';
|
||||
|
||||
|
@ -13,6 +15,6 @@ export class HeroSearchService {
|
|||
search(term: string): Observable<Hero[]> {
|
||||
return this.http
|
||||
.get(`app/heroes/?name=${term}`)
|
||||
.map((r: Response) => r.json().data as Hero[]);
|
||||
.map(response => response.json().data as Hero[]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
|
@ -55,12 +55,6 @@ block demos-list
|
|||
The root `AppComponent` orchestrates these demos:
|
||||
+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
|
||||
:marked
|
||||
# Providing HTTP services
|
||||
|
@ -196,7 +190,7 @@ a#HeroService
|
|||
:marked
|
||||
<a id="rxjs"></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.
|
||||
Instead you're calling a `map()` method.
|
||||
Clearly this is not a promise.
|
||||
|
@ -207,47 +201,24 @@ a#HeroService
|
|||
.l-main-section
|
||||
:marked
|
||||
## RxJS library
|
||||
[RxJS](https://github.com/ReactiveX/RxJS) ("Reactive Extensions") is a 3rd party library, endorsed by Angular,
|
||||
that implements the [*asynchronous observable*](https://www.youtube.com/watch?v=UHI0AzD_WfY "Rob Wormald on observables") pattern.
|
||||
<a href="http://reactivex.io/rxjs" target="_blank" title="RxJS Reactive Extensions">RxJS</a>
|
||||
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.
|
||||
|
||||
The app needs it when working with the HTTP client.
|
||||
Additionally, you must take a critical extra step to make RxJS observables usable.
|
||||
_This_ app needs it when working with the HTTP client.
|
||||
But you must take a critical extra step to make RxJS observables usable:
|
||||
you must import the RxJS operators individually.
|
||||
|
||||
### Enable RxJS operators
|
||||
The RxJS library is large.
|
||||
Size matters when building a production application and deploying it to mobile devices.
|
||||
You should include only necessary features.
|
||||
|
||||
Accordingly, Angular exposes a stripped down version of `Observable` in the `rxjs/Observable`
|
||||
module that lacks most of the operators such as the `map` method you
|
||||
called above in `getHeroes`.
|
||||
|
||||
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` — 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`.
|
||||
Each code file should add the operators it needs by importing from an RxJS library.
|
||||
The `getHeroes` method needs the `map` and `catch` operators so it imports them like this.
|
||||
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'rxjs-imports', 'app/app.component.ts (import rxjs)')(format=".")
|
||||
|
||||
.l-main-section
|
||||
a#extract-data
|
||||
|
@ -521,15 +492,13 @@ block wikipedia-jsonp+
|
|||
:marked
|
||||
### The WikiComponent
|
||||
|
||||
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.
|
||||
+makeExample('server-communication/ts/app/wiki/wiki.component.html', null, 'app/wiki/wiki.component.html')
|
||||
Now that you have a service that can query the Wikipedia API,
|
||||
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.ts', null, 'app/wiki/wiki.component.ts')
|
||||
:marked
|
||||
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.
|
||||
+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
|
||||
observable array of string results (`Observable<string[]>`).
|
||||
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.
|
||||
|
||||
### 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* .
|
||||
Here's how it will work after refactoring:
|
||||
figure.image-display
|
||||
|
@ -569,67 +538,71 @@ block wikipedia-jsonp+
|
|||
The application issues two search requests, one for *angular* and one for *http*.
|
||||
|
||||
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
|
||||
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>
|
||||
## 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
|
||||
user experience, create a copy of the `WikiComponent` instead and make it smarter.
|
||||
Here's the `WikiSmartComponent` which uses the same template.
|
||||
user experience, create a copy of the `WikiComponent` instead and make it smarter,
|
||||
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
|
||||
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
|
||||
|
||||
The template still binds to the search box `keyup` event and passes the complete search box value
|
||||
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='.')
|
||||
:marked
|
||||
The `WikiSmartComponent` turns the search box values into an observable _stream of search terms_
|
||||
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')
|
||||
The `WikiComponent` passes a new search term directly to the `WikipediaService` after every keystroke.
|
||||
|
||||
The `WikiSmartComponent` class turns the user's keystrokes into an observable _stream of search terms_
|
||||
with the help of a `Subject`, which you import from RxJS:
|
||||
+makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'import-subject')(format='.')
|
||||
:marked
|
||||
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.
|
||||
|
||||
+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
|
||||
### Listen for search terms
|
||||
|
||||
Earlier, you passed each search term directly to the service and bound the template to the service results.
|
||||
|
||||
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',
|
||||
'app/wiki/wiki-smart.component.ts')(format='.')
|
||||
|
||||
The `WikiSmartComponent` listens to the *stream of search terms* and
|
||||
processes that stream _before_ calling the service.
|
||||
+makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'observable-operators')(format='.')
|
||||
:marked
|
||||
Wait for the user to stop typing for at least 300 milliseconds
|
||||
([_debounceTime_](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/debounce.md)).
|
||||
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)).
|
||||
* <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>
|
||||
waits for the user to stop typing for at least 300 milliseconds.
|
||||
|
||||
The `WikipediaService` returns a separate observable of string arrays (`Observable<string[]>`) for each request.
|
||||
There could be multiple requests *in-flight*, all awaiting the server's reply,
|
||||
which means multiple *observables-of-strings* could arrive at any moment in any order.
|
||||
* <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>
|
||||
ensures that the service is called only when the new search term is different from the previous search term.
|
||||
|
||||
The [_switchMap_](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/flatmaplatest.md)
|
||||
(formerly known as `flatMapLatest`) returns a new observable that combines these `WikipediaService` observables,
|
||||
* 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>
|
||||
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,
|
||||
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
|
||||
.l-main-section
|
||||
:marked
|
||||
|
|
|
@ -168,6 +168,10 @@ block get-heroes-details
|
|||
|
||||
+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
|
||||
### Extracting the data in the *then* callback
|
||||
|
||||
|
@ -377,8 +381,9 @@ block observables-section-intro
|
|||
### Background
|
||||
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
|
||||
operators and extensions from the [RxJS Observables](http://reactivex.io/rxjs/) library.
|
||||
Angular core has basic support for observables.
|
||||
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.
|
||||
|
||||
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
|
||||
The `!{_priv}http.get()` call in `HeroSearchService` is similar to the one
|
||||
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
|
||||
|
||||
|
@ -498,26 +510,24 @@ block observable-transformers
|
|||
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.
|
||||
|
||||
a#rxjs-imports
|
||||
:marked
|
||||
### Import RxJS operators
|
||||
The RxJS operators are not available in Angular's base `Observable` implementation.
|
||||
We have to extend `Observable` by *importing* them.
|
||||
Most RxJS operators are not included in Angular's base `Observable` implementation.
|
||||
The base implementation includes only what Angular itself requires.
|
||||
|
||||
We could extend `Observable` with just the operators we need here by
|
||||
including the pertinent `import` statements at the top of this file.
|
||||
If we want more RxJS features, we have to extend `Observable` by *importing* the libraries in which they are defined.
|
||||
Here are all the RxJS imports _this_ component needs:
|
||||
|
||||
.l-sub-section
|
||||
: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='.')
|
||||
+makeExample('app/hero-search.component.ts','rxjs-imports','app/hero-search.component.ts (rxjs imports)')(format='.')
|
||||
|
||||
:marked
|
||||
We load them all at once by importing `rxjs-extensions` at the top of `AppModule`.
|
||||
|
||||
+makeExcerpt('app/app.module.ts', 'rxjs-extensions')(format='.')
|
||||
The `import 'rxjs/add/...'` syntax may be unfamiliar.
|
||||
It's missing the usual list of symbols between the braces: `{...}`.
|
||||
|
||||
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
|
||||
### Add the search component to the dashboard
|
||||
|
@ -570,7 +580,6 @@ block filetree
|
|||
.file hero-search.component.css (new)
|
||||
.file hero-search.component.ts (new)
|
||||
.file hero-search.service.ts (new)
|
||||
.file rxjs-extensions.ts
|
||||
.file hero.service.ts
|
||||
.file heroes.component.css
|
||||
.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.component.ts,
|
||||
toh-6/ts/app/hero-search.component.html,
|
||||
toh-6/ts/app/hero-search.component.css,
|
||||
toh-6/ts/app/rxjs-extensions.ts`,
|
||||
toh-6/ts/app/hero-search.component.css`,
|
||||
null,
|
||||
`hero-search.service.ts,
|
||||
hero-search.component.ts,
|
||||
hero-search.component.html,
|
||||
hero-search.component.css,
|
||||
rxjs-extensions.ts`
|
||||
hero-search.component.css`
|
||||
)
|
||||
|
||||
.l-sub-section
|
||||
|
|
Loading…
Reference in New Issue