docs(http): remove status 200 guard and add RxJS operators individually

This commit is contained in:
Ward Bell 2016-05-17 01:45:52 -07:00
parent f5a9edc68c
commit 594079970c
12 changed files with 54 additions and 61 deletions

View File

@ -47,9 +47,6 @@ class HeroService {
// #docregion extract-data // #docregion extract-data
dynamic _extractData(Response res) { dynamic _extractData(Response res) {
if (res.statusCode < 200 || res.statusCode >= 300) {
throw new Exception('Response status: ${res.statusCode}');
}
var body = JSON.decode(res.body); var body = JSON.decode(res.body);
// TODO: once fixed, https://github.com/adaojunior/http-in-memory-web-api/issues/1 // TODO: once fixed, https://github.com/adaojunior/http-in-memory-web-api/issues/1
// Drop the `?? body` term // Drop the `?? body` term

View File

@ -0,0 +1,9 @@
// #docregion
// import 'rxjs/Rx'; // adds ALL RxJS operators to Observable
// Just the Observable operators we need for THIS app.
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';

View File

@ -2,10 +2,10 @@
export class HeroData { export class HeroData {
createDb() { createDb() {
let heroes = [ let heroes = [
{ "id": "1", "name": "Windstorm" }, { id: '1', name: 'Windstorm' },
{ "id": "2", "name": "Bombasto" }, { id: '2', name: 'Bombasto' },
{ "id": "3", "name": "Magneta" }, { id: '3', name: 'Magneta' },
{ "id": "4", "name": "Tornado" } { id: '4', name: 'Tornado' }
]; ];
return {heroes}; return {heroes};
} }

View File

@ -6,8 +6,8 @@ import { HTTP_PROVIDERS } from '@angular/http';
// #enddocregion http-providers // #enddocregion http-providers
// #docregion import-rxjs // #docregion import-rxjs
// Add all operators to Observable // Add the RxJS Observable operators we need in this app.
import 'rxjs/Rx'; import './add-rxjs-operators';
// #enddocregion import-rxjs // #enddocregion import-rxjs
import { TohComponent } from './toh/toh.component'; import { TohComponent } from './toh/toh.component';

View File

@ -28,7 +28,7 @@ export class HeroListComponent implements OnInit {
} }
addHero (name: string) { addHero (name: string) {
if (!name) {return;} if (!name) { return; }
this.heroService.addHero(name) this.heroService.addHero(name)
.then( .then(
hero => this.heroes.push(hero), hero => this.heroes.push(hero),

View File

@ -31,7 +31,7 @@ export class HeroListComponent implements OnInit {
// #docregion addHero // #docregion addHero
addHero (name: string) { addHero (name: string) {
if (!name) {return;} if (!name) { return; }
this.heroService.addHero(name) this.heroService.addHero(name)
.subscribe( .subscribe(
hero => this.heroes.push(hero), hero => this.heroes.push(hero),

View File

@ -34,16 +34,14 @@ export class HeroService {
} }
private extractData(res: Response) { private extractData(res: Response) {
if (res.status < 200 || res.status >= 300) {
throw new Error('Bad response status: ' + res.status);
}
let body = res.json(); let body = res.json();
return body.data || { }; return body.data || { };
} }
private handleError (error: any) { private handleError (error: any) {
// In a real world app, we might send the error to remote logging infrastructure // In a real world app, we might use a remote logging infrastructure
let errMsg = error.message || 'Server error'; // We'd also dig deeper into the error to get a better message
let errMsg = error.message || error.statusText || 'Server error';
console.error(errMsg); // log to console instead console.error(errMsg); // log to console instead
return Promise.reject(errMsg); return Promise.reject(errMsg);
} }

View File

@ -43,9 +43,6 @@ export class HeroService {
// #docregion v1, extract-data // #docregion v1, extract-data
private extractData(res: Response) { private extractData(res: Response) {
if (res.status < 200 || res.status >= 300) {
throw new Error('Response status: ' + res.status);
}
let body = res.json(); let body = res.json();
return body.data || { }; return body.data || { };
} }
@ -54,7 +51,8 @@ export class HeroService {
// #docregion error-handling // #docregion error-handling
private handleError (error: any) { private handleError (error: any) {
// In a real world app, we might use a remote logging infrastructure // In a real world app, we might use a remote logging infrastructure
let errMsg = error.message || 'Server error'; // We'd also dig deeper into the error to get a better message
let errMsg = error.message || error.statusText || 'Server error';
console.error(errMsg); // log to console instead console.error(errMsg); // log to console instead
return Observable.throw(errMsg); return Observable.throw(errMsg);
} }

View File

@ -1,3 +1,4 @@
/* tslint:disable:member-ordering */
// #docregion // #docregion
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { JSONP_PROVIDERS } from '@angular/http'; import { JSONP_PROVIDERS } from '@angular/http';
@ -20,7 +21,7 @@ import { WikipediaService } from './wikipedia.service';
<li *ngFor="let item of items | async">{{item}}</li> <li *ngFor="let item of items | async">{{item}}</li>
</ul> </ul>
`, `,
providers:[JSONP_PROVIDERS, WikipediaService] providers: [JSONP_PROVIDERS, WikipediaService]
}) })
export class WikiSmartComponent { export class WikiSmartComponent {
@ -29,13 +30,13 @@ export class WikiSmartComponent {
// #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
// #docregion observable-operators // #docregion observable-operators
items:Observable<string[]> = this.searchTermStream items: Observable<string[]> = this.searchTermStream
.debounceTime(300) .debounceTime(300)
.distinctUntilChanged() .distinctUntilChanged()
.switchMap((term:string) => this.wikipediaService.search(term)); .switchMap((term: string) => this.wikipediaService.search(term));
// #enddocregion observable-operators // #enddocregion observable-operators
} }

View File

@ -17,7 +17,7 @@ import { WikipediaService } from './wikipedia.service';
<li *ngFor="let item of items | async">{{item}}</li> <li *ngFor="let item of items | async">{{item}}</li>
</ul> </ul>
`, `,
providers:[JSONP_PROVIDERS, WikipediaService] providers: [JSONP_PROVIDERS, WikipediaService]
}) })
export class WikiComponent { export class WikiComponent {

View File

@ -11,7 +11,7 @@ export class WikipediaService {
let wikiUrl = 'http://en.wikipedia.org/w/api.php'; let wikiUrl = 'http://en.wikipedia.org/w/api.php';
// #docregion search-parameters // #docregion search-parameters
var params = new URLSearchParams(); let params = new URLSearchParams();
params.set('search', term); // the user's search value params.set('search', term); // the user's search value
params.set('action', 'opensearch'); params.set('action', 'opensearch');
params.set('format', 'json'); params.set('format', 'json');

View File

@ -60,6 +60,7 @@ figure.image-display
:marked :marked
Here is the `TohComponent` shell: Here is the `TohComponent` shell:
+makeExample('server-communication/ts/app/toh/toh.component.ts', '', 'app/toh/toh.component.ts') +makeExample('server-communication/ts/app/toh/toh.component.ts', '', 'app/toh/toh.component.ts')
block http-providers block http-providers
:marked :marked
As usual, we import the symbols we need. The newcomer is `HTTP_PROVIDERS`, As usual, we import the symbols we need. The newcomer is `HTTP_PROVIDERS`,
@ -191,18 +192,26 @@ block rxjs
We should include only those features that we actually need. We should include only those features that we actually need.
Accordingly, Angular exposes a stripped down version of `Observable` in the `rxjs/Observable` module, Accordingly, Angular exposes a stripped down version of `Observable` in the `rxjs/Observable` module,
a version that lacks almost all operators including the ones we'd like to use here a version that lacks most of the operators including some we'd like to use here
such as the `map` method we called above in `getHeroes`. such as the `map` method we called above in `getHeroes`.
It's up to us to add the operators we need. It's up to us to add the operators we need.
We could add each operator, one-by-one, until we had a custom *Observable* implementation tuned
precisely to our requirements.
That would be a distraction today. We're learning HTTP, not counting bytes. We could add _every_ RxJS operators with a single import statement.
So we'll make it easy on ourselves and enrich *Observable* with the full set of operators. While that is the easiest thing to do, we'd pay a penalty in extended launch time and application size
It only takes one `import` statement. because the full library is so big. We only use a few operators in our app.
It's best to add that statement early when we're bootstrapping the application.
: Instead, we'll import each operator, one-by-one, until we have a custom *Observable* implementation tuned
precisely to our requirements. We'll put the `import` statements in one `app/add-rxjs-operators.ts` file.
+makeExample('server-communication/ts/app/add-rxjs-operators.ts', null, 'app/add-rxjs-operators.ts')(format=".")
:marked
If we forget an operator, the compiler will warn that it's missing and we'll update this file.
.l-sub-section
:marked
We don't need _all_ of these particular operators in the `HeroService` &mdash; just `map` and `catch`.
We'll need the others later, in a *Wiki* example [below](#more-observables).
:marked
Finally, we import `add-rxjs-operator`_itself_ in our `main.ts`:
+makeExample('server-communication/ts/app/main.ts', 'import-rxjs', 'app/main.ts (import rxjs)')(format=".") +makeExample('server-communication/ts/app/main.ts', 'import-rxjs', 'app/main.ts (import rxjs)')(format=".")
a#extract-data a#extract-data
@ -212,31 +221,8 @@ a#extract-data
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'extract-data', 'app/toh/hero.service.ts (excerpt)')(format=".") +makeExample('server-communication/ts/app/toh/hero.service.ts', 'extract-data', 'app/toh/hero.service.ts (excerpt)')(format=".")
:marked :marked
The `response` object does not hold our data in a form we can use directly. The `response` object does not hold our data in a form we can use directly.
To make it useful in our application we must To make it useful in our application we must parse the response data into a JSON object
* check the response status,
* parse the response data into a JSON object
+ifDocsFor('ts')
.alert.is-important
:marked
*Beta alert*: error status interception and parsing may be absorbed within `http` when Angular is released.
:marked
#### Non-success status codes
A status code outside the 200-299 range denotes an error from the _application point of view_
but it is not an error from the _HTTP point of view_.
For example, a `404 - Not Found` is a response like any other.
The request went out; a response came back; here it is, thank you very much.
We'd have an exception only if `#{_priv}http` failed to operate (e.g., it errored internally).
block non-success-status-codes
:marked
Because a status code outside the 200-299 range _is an error_ from the application point of view,
we intercept it and throw, moving the observable chain to the error path.
The `catch` operator that is next in the `getHeroes` observable chain will handle our thrown error.
:marked
#### Parse to JSON #### Parse to JSON
block parse-json block parse-json
:marked :marked
@ -597,7 +583,7 @@ block wikipedia-jsonp+
+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')(format='.')
:marked :marked
We wait for the user to stop typing for at least 300 milliseconds We wait for the user to stop typing for at least 300 milliseconds
([debounce](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/debounce.md)). ([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 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)). ([distinctUntilChanged](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/distinctuntilchanged.md)).
@ -611,6 +597,10 @@ block wikipedia-jsonp+
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. The displayed list of search results stays in sync with the user's sequence of search terms.
.l-sub-section
:marked
We added the `debounceTime`, `distinctUntilChanged`, and `switchMap` operators to the RxJS `Observable` class
in `add-rxjs-operators` as [described above](#rxjs)
a#in-mem-web-api a#in-mem-web-api
.l-main-section .l-main-section