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
|
// #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({
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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
|
// #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
|
|
||||||
|
|
|
@ -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';
|
|
|
@ -4,6 +4,11 @@
|
||||||
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';
|
||||||
|
|
||||||
|
// #docregion rxjs-imports
|
||||||
|
import 'rxjs/add/operator/toPromise';
|
||||||
|
// #enddocregion rxjs-imports
|
||||||
|
|
||||||
import { Hero } from './hero';
|
import { Hero } from './hero';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|
|
@ -10,8 +10,13 @@ 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 {
|
||||||
|
|
|
@ -2,7 +2,13 @@
|
||||||
// #docplaster
|
// #docplaster
|
||||||
// #docregion
|
// #docregion
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
// #docregion rxjs-imports
|
||||||
import { Observable } from 'rxjs/Observable';
|
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
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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';
|
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) { }
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) { }
|
||||||
|
|
|
@ -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) {}
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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 { 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';
|
||||||
|
|
||||||
|
|
|
@ -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';
|
||||||
|
|
||||||
|
|
|
@ -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';
|
||||||
|
|
||||||
|
|
|
@ -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';
|
||||||
|
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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[]>([]);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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[]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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:
|
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` — 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`,
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue