diff --git a/public/docs/_examples/pipes/ts/app/fetch-json.pipe.ts b/public/docs/_examples/pipes/ts/app/fetch-json.pipe.ts index 7f89aeac23..4d56e865f3 100644 --- a/public/docs/_examples/pipes/ts/app/fetch-json.pipe.ts +++ b/public/docs/_examples/pipes/ts/app/fetch-json.pipe.ts @@ -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({ diff --git a/public/docs/_examples/pipes/ts/app/hero-async-message.component.ts b/public/docs/_examples/pipes/ts/app/hero-async-message.component.ts index 54e5b61030..d5bbd9fb0e 100644 --- a/public/docs/_examples/pipes/ts/app/hero-async-message.component.ts +++ b/public/docs/_examples/pipes/ts/app/hero-async-message.component.ts @@ -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', diff --git a/public/docs/_examples/pipes/ts/app/rxjs-extensions.ts b/public/docs/_examples/pipes/ts/app/rxjs-extensions.ts deleted file mode 100644 index 99da6a56cb..0000000000 --- a/public/docs/_examples/pipes/ts/app/rxjs-extensions.ts +++ /dev/null @@ -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'; diff --git a/public/docs/_examples/server-communication/ts/app/app.component.ts b/public/docs/_examples/server-communication/ts/app/app.component.ts index 25ada8f907..780d044cab 100644 --- a/public/docs/_examples/server-communication/ts/app/app.component.ts +++ b/public/docs/_examples/server-communication/ts/app/app.component.ts @@ -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 diff --git a/public/docs/_examples/server-communication/ts/app/app.module.1.ts b/public/docs/_examples/server-communication/ts/app/app.module.1.ts index 7f887a4356..fb7012aa02 100644 --- a/public/docs/_examples/server-communication/ts/app/app.module.1.ts +++ b/public/docs/_examples/server-communication/ts/app/app.module.1.ts @@ -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'; diff --git a/public/docs/_examples/server-communication/ts/app/app.module.ts b/public/docs/_examples/server-communication/ts/app/app.module.ts index ae35cc2787..fd0c720c3c 100644 --- a/public/docs/_examples/server-communication/ts/app/app.module.ts +++ b/public/docs/_examples/server-communication/ts/app/app.module.ts @@ -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'; diff --git a/public/docs/_examples/server-communication/ts/app/rxjs-operators.ts b/public/docs/_examples/server-communication/ts/app/rxjs-operators.ts deleted file mode 100644 index 0a9b9fcc88..0000000000 --- a/public/docs/_examples/server-communication/ts/app/rxjs-operators.ts +++ /dev/null @@ -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'; diff --git a/public/docs/_examples/server-communication/ts/app/toh/hero.service.promise.ts b/public/docs/_examples/server-communication/ts/app/toh/hero.service.promise.ts index 58fbec8e29..e38bd4bebf 100644 --- a/public/docs/_examples/server-communication/ts/app/toh/hero.service.promise.ts +++ b/public/docs/_examples/server-communication/ts/app/toh/hero.service.promise.ts @@ -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 { diff --git a/public/docs/_examples/server-communication/ts/app/toh/hero.service.ts b/public/docs/_examples/server-communication/ts/app/toh/hero.service.ts index c87e70b4b8..804883d0e8 100644 --- a/public/docs/_examples/server-communication/ts/app/toh/hero.service.ts +++ b/public/docs/_examples/server-communication/ts/app/toh/hero.service.ts @@ -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 { diff --git a/public/docs/_examples/server-communication/ts/app/wiki/wiki-smart.component.ts b/public/docs/_examples/server-communication/ts/app/wiki/wiki-smart.component.ts index 31d7ae731e..ec95472b98 100644 --- a/public/docs/_examples/server-communication/ts/app/wiki/wiki-smart.component.ts +++ b/public/docs/_examples/server-communication/ts/app/wiki/wiki-smart.component.ts @@ -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: ` +

Smarter Wikipedia Demo

+

Search when typing stops

+ + `, providers: [ WikipediaService ] }) export class WikiSmartComponent implements OnInit { - title = 'Smarter Wikipedia Demo'; - fetches = 'Fetches when typing stops'; items: Observable; + constructor (private wikipediaService: WikipediaService) {} + // #docregion subject private searchTermStream = new Subject(); search(term: string) { this.searchTermStream.next(term); } // #enddocregion subject - constructor (private wikipediaService: WikipediaService) {} - ngOnInit() { // #docregion observable-operators this.items = this.searchTermStream diff --git a/public/docs/_examples/server-communication/ts/app/wiki/wiki.component.html b/public/docs/_examples/server-communication/ts/app/wiki/wiki.component.html deleted file mode 100644 index e94d37f3aa..0000000000 --- a/public/docs/_examples/server-communication/ts/app/wiki/wiki.component.html +++ /dev/null @@ -1,11 +0,0 @@ - -

{{title}}

-

{{fetches}}

- - - - - - diff --git a/public/docs/_examples/server-communication/ts/app/wiki/wiki.component.ts b/public/docs/_examples/server-communication/ts/app/wiki/wiki.component.ts index f72f005460..4230df12a1 100644 --- a/public/docs/_examples/server-communication/ts/app/wiki/wiki.component.ts +++ b/public/docs/_examples/server-communication/ts/app/wiki/wiki.component.ts @@ -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: ` +

Wikipedia Demo

+

Search after each keystroke

+ + `, providers: [ WikipediaService ] }) export class WikiComponent { - title = 'Wikipedia Demo'; - fetches = 'Fetches after each keystroke'; items: Observable; + constructor (private wikipediaService: WikipediaService) { } + search (term: string) { this.items = this.wikipediaService.search(term); } - - constructor (private wikipediaService: WikipediaService) { } } diff --git a/public/docs/_examples/server-communication/ts/app/wiki/wikipedia.service.1.ts b/public/docs/_examples/server-communication/ts/app/wiki/wikipedia.service.1.ts index 25911b0d37..5cbcb7d707 100644 --- a/public/docs/_examples/server-communication/ts/app/wiki/wikipedia.service.1.ts +++ b/public/docs/_examples/server-communication/ts/app/wiki/wikipedia.service.1.ts @@ -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) { } diff --git a/public/docs/_examples/server-communication/ts/app/wiki/wikipedia.service.ts b/public/docs/_examples/server-communication/ts/app/wiki/wikipedia.service.ts index 742dfed1b9..a38167d1c6 100644 --- a/public/docs/_examples/server-communication/ts/app/wiki/wikipedia.service.ts +++ b/public/docs/_examples/server-communication/ts/app/wiki/wikipedia.service.ts @@ -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) {} diff --git a/public/docs/_examples/style-guide/ts/04-11/app/core/index.ts b/public/docs/_examples/style-guide/ts/04-11/app/core/index.ts index 7d2092f804..098f40c7d5 100644 --- a/public/docs/_examples/style-guide/ts/04-11/app/core/index.ts +++ b/public/docs/_examples/style-guide/ts/04-11/app/core/index.ts @@ -1,5 +1,4 @@ // #docregion export * from './logger.service'; -export * from './rxjs-extensions'; export * from './spinner/spinner.service'; export * from './nav/nav.component'; diff --git a/public/docs/_examples/style-guide/ts/04-11/app/core/rxjs-extensions.ts b/public/docs/_examples/style-guide/ts/04-11/app/core/rxjs-extensions.ts deleted file mode 100644 index 870dd7af2b..0000000000 --- a/public/docs/_examples/style-guide/ts/04-11/app/core/rxjs-extensions.ts +++ /dev/null @@ -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'; diff --git a/public/docs/_examples/style-guide/ts/05-15/app/heroes/hero-list/hero-list.component.avoid.ts b/public/docs/_examples/style-guide/ts/05-15/app/heroes/hero-list/hero-list.component.avoid.ts index b66bb9e81f..c323ba2b1c 100644 --- a/public/docs/_examples/style-guide/ts/05-15/app/heroes/hero-list/hero-list.component.avoid.ts +++ b/public/docs/_examples/style-guide/ts/05-15/app/heroes/hero-list/hero-list.component.avoid.ts @@ -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'; diff --git a/public/docs/_examples/style-guide/ts/05-15/app/heroes/shared/hero.service.ts b/public/docs/_examples/style-guide/ts/05-15/app/heroes/shared/hero.service.ts index 3f83d92fe8..72d07bbed4 100644 --- a/public/docs/_examples/style-guide/ts/05-15/app/heroes/shared/hero.service.ts +++ b/public/docs/_examples/style-guide/ts/05-15/app/heroes/shared/hero.service.ts @@ -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'; diff --git a/public/docs/_examples/style-guide/ts/07-03/app/heroes/shared/hero.service.ts b/public/docs/_examples/style-guide/ts/07-03/app/heroes/shared/hero.service.ts index 3f83d92fe8..72d07bbed4 100644 --- a/public/docs/_examples/style-guide/ts/07-03/app/heroes/shared/hero.service.ts +++ b/public/docs/_examples/style-guide/ts/07-03/app/heroes/shared/hero.service.ts @@ -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'; diff --git a/public/docs/_examples/style-guide/ts/07-04/app/heroes/shared/hero.service.ts b/public/docs/_examples/style-guide/ts/07-04/app/heroes/shared/hero.service.ts index 3f83d92fe8..72d07bbed4 100644 --- a/public/docs/_examples/style-guide/ts/07-04/app/heroes/shared/hero.service.ts +++ b/public/docs/_examples/style-guide/ts/07-04/app/heroes/shared/hero.service.ts @@ -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'; diff --git a/public/docs/_examples/toh-6/ts/app/app.module.ts b/public/docs/_examples/toh-6/ts/app/app.module.ts index c0933e4697..58eeb10c54 100644 --- a/public/docs/_examples/toh-6/ts/app/app.module.ts +++ b/public/docs/_examples/toh-6/ts/app/app.module.ts @@ -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'; diff --git a/public/docs/_examples/toh-6/ts/app/hero-search.component.ts b/public/docs/_examples/toh-6/ts/app/hero-search.component.ts index 78c490ef93..949ef897db 100644 --- a/public/docs/_examples/toh-6/ts/app/hero-search.component.ts +++ b/public/docs/_examples/toh-6/ts/app/hero-search.component.ts @@ -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([])) .catch(error => { - // TODO: real error handling + // TODO: add real error handling console.log(error); return Observable.of([]); }); diff --git a/public/docs/_examples/toh-6/ts/app/hero-search.service.ts b/public/docs/_examples/toh-6/ts/app/hero-search.service.ts index ae2e47670a..d24e0fba41 100644 --- a/public/docs/_examples/toh-6/ts/app/hero-search.service.ts +++ b/public/docs/_examples/toh-6/ts/app/hero-search.service.ts @@ -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 { return this.http .get(`app/heroes/?name=${term}`) - .map((r: Response) => r.json().data as Hero[]); + .map(response => response.json().data as Hero[]); } } diff --git a/public/docs/_examples/toh-6/ts/app/rxjs-extensions.ts b/public/docs/_examples/toh-6/ts/app/rxjs-extensions.ts deleted file mode 100644 index a0facfe03e..0000000000 --- a/public/docs/_examples/toh-6/ts/app/rxjs-extensions.ts +++ /dev/null @@ -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'; diff --git a/public/docs/ts/latest/guide/server-communication.jade b/public/docs/ts/latest/guide/server-communication.jade index 72bfdf5aed..d82c9fdea2 100644 --- a/public/docs/ts/latest/guide/server-communication.jade +++ b/public/docs/ts/latest/guide/server-communication.jade @@ -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 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). + promise. 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. + RxJS + is a third party library, endorsed by Angular, that implements the + asynchronous observable 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 `` 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`). 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. ## 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)). + * debounceTime + waits for the user to stop typing for at least 300 milliseconds. - The `WikipediaService` returns a separate observable of string arrays (`Observable`) 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. + * distinctUntilChanged + 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 switchMap + 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`) 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 diff --git a/public/docs/ts/latest/tutorial/toh-pt6.jade b/public/docs/ts/latest/tutorial/toh-pt6.jade index 188d404222..2dd073e1ba 100644 --- a/public/docs/ts/latest/tutorial/toh-pt6.jade +++ b/public/docs/ts/latest/tutorial/toh-pt6.jade @@ -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 + RxJS library. 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. - Another notable difference: we no longer call `toPromise`, - we simply return the *observable* instead. + + 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, map, + to extract heroes from the response data. + + RxJS operator chaining makes response processing easy and readable. + See the [discuss below about operators](#rxjs-imports). + ### 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