diff --git a/public/docs/_examples/server-communication/ts/app/main.ts b/public/docs/_examples/server-communication/ts/app/main.ts index c992809065..a7c4bab464 100644 --- a/public/docs/_examples/server-communication/ts/app/main.ts +++ b/public/docs/_examples/server-communication/ts/app/main.ts @@ -1,15 +1,21 @@ -//#docregion -import {bootstrap} from 'angular2/platform/browser'; +// #docplaster +// #docregion +import { bootstrap } from 'angular2/platform/browser'; +// #docregion http-providers +import { HTTP_PROVIDERS } from 'angular2/http'; +// #enddocregion http-providers // #docregion import-rxjs // Add all operators to Observable import 'rxjs/Rx'; // #enddocregion import-rxjs -import {WikiComponent} from './wiki/wiki.component'; -import {WikiSmartComponent} from './wiki/wiki-smart.component'; -import {TohComponent} from './toh/toh.component'; +import { WikiComponent } from './wiki/wiki.component'; +import { WikiSmartComponent } from './wiki/wiki-smart.component'; +import { TohComponent } from './toh/toh.component'; bootstrap(WikiComponent); bootstrap(WikiSmartComponent); -bootstrap(TohComponent); \ No newline at end of file +// #docregion http-providers +bootstrap(TohComponent, [HTTP_PROVIDERS]); +// #enddocregion http-providers diff --git a/public/docs/_examples/server-communication/ts/app/toh/hero-list.component.1.ts b/public/docs/_examples/server-communication/ts/app/toh/hero-list.component.1.ts index 2a96ce2df3..4506bab39f 100644 --- a/public/docs/_examples/server-communication/ts/app/toh/hero-list.component.1.ts +++ b/public/docs/_examples/server-communication/ts/app/toh/hero-list.component.1.ts @@ -1,3 +1,4 @@ +// ToH Promise Version // #docregion import {Component, OnInit} from 'angular2/core'; import {Hero} from './hero'; @@ -5,22 +6,7 @@ import {HeroService} from './hero.service.1'; @Component({ selector: 'hero-list', -// #docregion template - template: ` -

Heroes:

- - New Hero: - - -
{{errorMessage}}
- `, - // #enddocregion template + templateUrl: 'app/toh/hero-list.component.html', styles: ['.error {color:red;}'] }) // #docregion component @@ -29,7 +15,7 @@ export class HeroListComponent implements OnInit { constructor (private _heroService: HeroService) {} errorMessage: string; - heroes:Hero[]; + heroes: Hero[]; ngOnInit() { this.getHeroes(); } diff --git a/public/docs/_examples/server-communication/ts/app/toh/hero-list.component.html b/public/docs/_examples/server-communication/ts/app/toh/hero-list.component.html new file mode 100644 index 0000000000..22105a6b15 --- /dev/null +++ b/public/docs/_examples/server-communication/ts/app/toh/hero-list.component.html @@ -0,0 +1,13 @@ + +

Heroes:

+ +New Hero: + + +
{{errorMessage}}
diff --git a/public/docs/_examples/server-communication/ts/app/toh/hero-list.component.ts b/public/docs/_examples/server-communication/ts/app/toh/hero-list.component.ts index a55cf35a72..088ffc56a6 100644 --- a/public/docs/_examples/server-communication/ts/app/toh/hero-list.component.ts +++ b/public/docs/_examples/server-communication/ts/app/toh/hero-list.component.ts @@ -5,20 +5,7 @@ import {HeroService} from './hero.service'; @Component({ selector: 'hero-list', - template: ` -

Heroes:

- - New Hero: - - -
{{errorMessage}}
- `, + templateUrl: 'app/toh/hero-list.component.html', styles: ['.error {color:red;}'] }) // #docregion component diff --git a/public/docs/_examples/server-communication/ts/app/toh/hero.service.1.ts b/public/docs/_examples/server-communication/ts/app/toh/hero.service.1.ts index 05b97fa75d..8ac1d92a2c 100644 --- a/public/docs/_examples/server-communication/ts/app/toh/hero.service.1.ts +++ b/public/docs/_examples/server-communication/ts/app/toh/hero.service.1.ts @@ -1,9 +1,9 @@ -/* Promise version */ +// ToH Promise Version // #docplaster // #docregion import {Injectable} from 'angular2/core'; -import {Http} from 'angular2/http'; +import {Http, Response} from 'angular2/http'; import {Headers, RequestOptions} from 'angular2/http'; import {Hero} from './hero'; @@ -15,27 +15,36 @@ export class HeroService { private _heroesUrl = 'app/heroes.json'; // #docregion methods - getHeroes () { + getHeroes (): Promise { return this.http.get(this._heroesUrl) .toPromise() - .then(res => res.json().data, this.handleError) - .then(data => { console.log(data); return data; }); // eyeball results in the console + .then(this.extractData) + .catch(this.handleError); } - addHero (name: string) : Promise { + addHero (name: string): Promise { let body = JSON.stringify({ name }); let headers = new Headers({ 'Content-Type': 'application/json' }); let options = new RequestOptions({ headers: headers }); return this.http.post(this._heroesUrl, body, options) .toPromise() - .then(res => res.json().data) + .then(this.extractData) .catch(this.handleError); } + + private extractData(res: Response) { + if (res.status < 200 || res.status >= 300) { + throw new Error('Bad response status: ' + res.status); + } + let body = res.json(); + return body.data || { }; + } + private handleError (error: any) { - // in a real world app, we may send the error to some remote logging infrastructure - console.error(error); // log to console instead + // In a real world app, we might send the error to remote logging infrastructure let errMsg = error.message || 'Server error'; + console.error(errMsg); // log to console instead return Promise.reject(errMsg); } 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 4e9a0d9070..0ada9137fc 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 @@ -3,7 +3,7 @@ // #docregion // #docregion v1 import {Injectable} from 'angular2/core'; -import {Http} from 'angular2/http'; +import {Http, Response} from 'angular2/http'; // #enddocregion v1 // #docregion import-request-options import {Headers, RequestOptions} from 'angular2/http'; @@ -31,18 +31,13 @@ export class HeroService { // #enddocregion endpoint // #docregion methods - // #docregion error-handling + // #docregion error-handling, http-get getHeroes (): Observable { - // #docregion http-get, http-get-v1 return this.http.get(this._heroesUrl) .map(this.extractData) - // #enddocregion v1, http-get-v1, error-handling - .do(data => console.log(data)) // eyeball results in the console - // #docregion v1, http-get-v1, error-handling .catch(this.handleError); - // #enddocregion http-get, http-get-v1 } - // #enddocregion error-handling + // #enddocregion error-handling, http-get // #enddocregion v1 // #docregion addhero @@ -62,6 +57,7 @@ export class HeroService { // #docregion v1 + // #docregion extract-data private extractData(res: Response) { if (res.status < 200 || res.status >= 300) { throw new Error('Bad response status: ' + res.status); @@ -69,12 +65,13 @@ export class HeroService { let body = res.json(); return body.data || { }; } + // #enddocregion extract-data // #docregion error-handling private handleError (error: any) { - // in a real world app, we may send the error to some remote logging infrastructure - console.error(error); // log to console instead + // In a real world app, we might send the error to remote logging infrastructure let errMsg = error.message || 'Server error'; + console.error(errMsg); // log to console instead return Observable.throw(errMsg); } // #enddocregion error-handling diff --git a/public/docs/_examples/server-communication/ts/app/toh/toh.component.1.ts b/public/docs/_examples/server-communication/ts/app/toh/toh.component.1.ts new file mode 100644 index 0000000000..d689f73fd3 --- /dev/null +++ b/public/docs/_examples/server-communication/ts/app/toh/toh.component.1.ts @@ -0,0 +1,32 @@ +// ToH Promise Version +console.log ('Promise version'); + +import { Component } from 'angular2/core'; +import { HTTP_PROVIDERS } from 'angular2/http'; + +import { HeroListComponent } from './hero-list.component.1'; +import { HeroService } from './hero.service.1'; + +import { provide } from 'angular2/core'; +import { XHRBackend } from 'angular2/http'; + +import { InMemoryBackendService, + SEED_DATA } from 'a2-in-memory-web-api/core'; +import { HeroData } from '../hero-data'; + +@Component({ + selector: 'my-toh', + template: ` +

Tour of Heroes

+ + `, + directives:[HeroListComponent], + providers: [ + HTTP_PROVIDERS, + HeroService, + // in-memory web api providers + provide(XHRBackend, { useClass: InMemoryBackendService }), // in-mem server + provide(SEED_DATA, { useClass: HeroData }) // in-mem server data + ] +}) +export class TohComponent { } diff --git a/public/docs/_examples/server-communication/ts/app/toh/toh.component.2.ts b/public/docs/_examples/server-communication/ts/app/toh/toh.component.2.ts new file mode 100644 index 0000000000..f6efb282a5 --- /dev/null +++ b/public/docs/_examples/server-communication/ts/app/toh/toh.component.2.ts @@ -0,0 +1,44 @@ +// #docplaster + +// #docregion +import { Component } from 'angular2/core'; +import { HTTP_PROVIDERS } from 'angular2/http'; + +import { HeroListComponent } from './hero-list.component'; +import { HeroService } from './hero.service'; +// #enddocregion + +// #docregion in-mem-web-api-imports +import { provide } from 'angular2/core'; +import { XHRBackend } from 'angular2/http'; + +// in-memory web api imports +import { InMemoryBackendService, + SEED_DATA } from 'a2-in-memory-web-api/core'; +import { HeroData } from '../hero-data'; +// #enddocregion in-mem-web-api-imports +// #docregion + +@Component({ + selector: 'my-toh', +// #docregion template + template: ` +

Tour of Heroes

+ + `, + // #enddocregion template + directives: [HeroListComponent], + providers: [ + HTTP_PROVIDERS, + HeroService, +// #enddocregion +// #docregion in-mem-web-api-providers + // in-memory web api providers + provide(XHRBackend, { useClass: InMemoryBackendService }), // in-mem server + provide(SEED_DATA, { useClass: HeroData }) // in-mem server data +// #enddocregion in-mem-web-api-providers +// #docregion + ] +}) +export class TohComponent { } +// #enddocregion diff --git a/public/docs/_examples/server-communication/ts/app/toh/toh.component.ts b/public/docs/_examples/server-communication/ts/app/toh/toh.component.ts index bc93e5c9c9..f6efb282a5 100644 --- a/public/docs/_examples/server-communication/ts/app/toh/toh.component.ts +++ b/public/docs/_examples/server-communication/ts/app/toh/toh.component.ts @@ -1,24 +1,23 @@ // #docplaster // #docregion -import {Component} from 'angular2/core'; -import {HTTP_PROVIDERS} from 'angular2/http'; +import { Component } from 'angular2/core'; +import { HTTP_PROVIDERS } from 'angular2/http'; -import {Hero} from './hero'; -import {HeroListComponent} from './hero-list.component'; -import {HeroService} from './hero.service'; -//#enddocregion +import { HeroListComponent } from './hero-list.component'; +import { HeroService } from './hero.service'; +// #enddocregion -//#docregion in-mem-web-api-imports -import {provide} from 'angular2/core'; -import {XHRBackend} from 'angular2/http'; +// #docregion in-mem-web-api-imports +import { provide } from 'angular2/core'; +import { XHRBackend } from 'angular2/http'; // in-memory web api imports -import {InMemoryBackendService, - SEED_DATA} from 'a2-in-memory-web-api/core'; -import {HeroData} from '../hero-data'; +import { InMemoryBackendService, + SEED_DATA } from 'a2-in-memory-web-api/core'; +import { HeroData } from '../hero-data'; // #enddocregion in-mem-web-api-imports -//#docregion +// #docregion @Component({ selector: 'my-toh', @@ -28,17 +27,17 @@ import {HeroData} from '../hero-data'; `, // #enddocregion template - directives:[HeroListComponent], - providers: [ + directives: [HeroListComponent], + providers: [ HTTP_PROVIDERS, HeroService, -//#enddocregion -//#docregion in-mem-web-api-providers +// #enddocregion +// #docregion in-mem-web-api-providers // in-memory web api providers provide(XHRBackend, { useClass: InMemoryBackendService }), // in-mem server provide(SEED_DATA, { useClass: HeroData }) // in-mem server data -//#enddocregion in-mem-web-api-providers -//#docregion +// #enddocregion in-mem-web-api-providers +// #docregion ] }) export class TohComponent { } diff --git a/public/docs/_examples/testing/ts/app/http-hero.service.ts b/public/docs/_examples/testing/ts/app/http-hero.service.ts index 40c2f37866..86f3dcf8d2 100644 --- a/public/docs/_examples/testing/ts/app/http-hero.service.ts +++ b/public/docs/_examples/testing/ts/app/http-hero.service.ts @@ -36,7 +36,7 @@ export class HeroService { } private handleError (error: any) { - // in a real world app, we may send the error to some remote logging infrastructure + // In a real world app, we might send the error to remote logging infrastructure let errMsg = error.message || 'Server error'; console.error(errMsg); // log to console instead return Observable.throw(errMsg); diff --git a/public/docs/ts/latest/guide/server-communication.jade b/public/docs/ts/latest/guide/server-communication.jade index eefc531262..248aa33c14 100644 --- a/public/docs/ts/latest/guide/server-communication.jade +++ b/public/docs/ts/latest/guide/server-communication.jade @@ -21,7 +21,6 @@ include ../_util-fns [Enabling RxJS Operators](#enable-rxjs-operators)
[Extract JSON data with RxJS map](#map)
[Error handling](#error-handling)
- [Log results to console](#do)
[Send data to the server](#update)
[Add headers](#headers)
[Promises instead of observables](#promises)
@@ -67,14 +66,20 @@ figure.image-display making them available to the child components of this "Tour of Heroes" application. .l-sub-section + :marked + Alternatively, we may choose to add the `HTTP_PROVIDERS` while bootstrapping the app: + +makeExample('server-communication/ts/app/main.ts','http-providers','app/main.ts')(format='.') :marked Learn about providers in the [Dependency Injection](dependency-injection.html) chapter. + :marked - This sample only has one child, the `HeroListComponent` shown here in full: -+makeExample('server-communication/ts/app/toh/hero-list.component.ts', null, 'app/toh/hero-list.component.ts') + This sample only has one child, the `HeroListComponent`. Here's its template: ++makeExample('server-communication/ts/app/toh/hero-list.component.html', null, 'app/toh/hero-list.component.html (Template)') :marked The component template displays a list of heroes with the `NgFor` repeater directive. - +figure.image-display + img(src='/resources/images/devguide/server-communication/hero-list.png' alt="Hero List") +:marked Beneath the heroes is an input box and an *Add Hero* button where we can enter the names of new heroes and add them to the database. We use a [local template variable](template-syntax.html#local-vars), `newHero`, to access the @@ -82,22 +87,26 @@ figure.image-display When the user clicks the button, we pass that value to the component's `addHero` method and then clear it to make ready for a new hero name. - Below the button is an optional error message. + Below the button is a (hidden) area for an error message. a(id="oninit") a(id="HeroListComponent") :marked ### The *HeroListComponent* class + Here's the component class: ++makeExample('server-communication/ts/app/toh/hero-list.component.ts','component', 'app/toh/hero-list.component.ts (class)') +:marked We [inject](dependency-injection.html) the `HeroService` into the constructor. That's the instance of the `HeroService` that we provided in the parent shell `TohComponent`. Notice that the component **does not talk to the server directly!** The component doesn't know or care how we get the data. Those details it delegates to the `heroService` class (which we'll get to in a moment). - This is a golden rule: *always delegate data access to a supporting service class*. + + This is a golden rule: **always delegate data access to a supporting service class**. - Although the component should request heroes immediately, - we do **not** call the service `get` method in the component's constructor. + Although _at runtime_ the component requests heroes immediately after creation, + we do **not** call the service's `get` method in the component's constructor. We call it inside the `ngOnInit` [lifecycle hook](lifecycle-hooks.html) instead and count on Angular to call `ngOnInit` when it instantiates this component. .l-sub-section @@ -106,8 +115,9 @@ a(id="HeroListComponent") Components are easier to test and debug when their constructors are simple and all real work (especially calling a remote server) is handled in a separate method. :marked - The service `get` and `addHero` methods return an `Observable` of HTTP Responses to which we `subscribe`, - specifying the actions to take if a method succeeds or fails. + The service `get` and `addHero` methods return an `Observable` of HTTP hero data. + We subscribe to this `Observable`, + specifying the actions to take when the request succeeds or fails. We'll get to observables and subscription shortly. With our basic intuitions about the component squared away, we can turn to development of the backend data source @@ -122,7 +132,7 @@ a(id="HeroListComponent") In this chapter, we get the heroes from the server using Angular's own HTTP Client service. Here's the new `HeroService`: -+makeExample('server-communication/ts/app/toh/hero.service.ts', 'v1', 'app/toh/hero.service.ts')(format=".") ++makeExample('server-communication/ts/app/toh/hero.service.ts', 'v1', 'app/toh/hero.service.ts') :marked We begin by importing Angular's `Http` client service and [inject it](dependency-injection.html) into the `HeroService` constructor. @@ -133,7 +143,7 @@ a(id="HeroListComponent") +makeExample('server-communication/ts/index.html', 'http', 'index.html')(format=".") :marked Look closely at how we call `http.get` -+makeExample('server-communication/ts/app/toh/hero.service.ts', 'http-get-v1', 'app/toh/hero.service.ts (http.get)')(format=".") ++makeExample('server-communication/ts/app/toh/hero.service.ts', 'http-get', 'app/toh/hero.service.ts (getHeroes)')(format=".") :marked We pass the resource URL to `get` and it calls the server which should return heroes. .l-sub-section @@ -153,13 +163,6 @@ a(id="HeroListComponent") In fact, the `http.get` method returns an **Observable** of HTTP Responses (`Observable`) from the RxJS library and `map` is one of the RxJS *operators*. -.callout.is-important - header HTTP GET Delayed - :marked - The `http.get` does **not send the request just yet!** This observable is - [*cold*](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/creating.md#cold-vs-hot-observables) - which means the request won't go out until something *subscribes* to the observable. - That *something* is the [HeroListComponent](#subscribe). .l-main-section :marked @@ -193,15 +196,37 @@ a(id="HeroListComponent") It's best to add that statement early when we're bootstrapping the application. : +makeExample('server-communication/ts/app/main.ts', 'import-rxjs', 'app/main.ts (import rxjs)')(format=".") + +a(id="map") +a(id="extract-data") :marked - - ### Map the response object - Let's come back to the `HeroService` and look at the `http.get` call again to see why we needed `map()` -+makeExample('server-communication/ts/app/toh/hero.service.ts', 'http-get-v1', 'app/toh/hero.service.ts (http.get)')(format=".") + ### Process the response object + Remember that our `getHeroes` method mapped the `http.get` response object to heroes with an `extractData` helper method: ++makeExample('server-communication/ts/app/toh/hero.service.ts', 'extract-data', 'app/toh/hero.service.ts (extractData)')(format=".") :marked The `response` object does not hold our data in a form we can use directly. - It takes an additional step — calling `response.json()` — to transform the bytes from the server into a JSON object. + To make it useful in our application we must + * check for a bad response + * parse the response data into a JSON object +.alert.is-important + :marked + *Beta alert*: error status interception and parsing may be absorbed within `http` when Angular is released. +:marked + #### Bad status codes + A status code outside the 200-300 range is 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 observable error only if `http` failed to operate (e.g., it errored internally). + + Because a status code outside the 200-300 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. + #### Parse to JSON + The response data are in JSON string form. + We must parse that string into JavaScript objects which we do by calling `response.json()`. .l-sub-section :marked This is not Angular's own design. @@ -228,8 +253,18 @@ a(id="HeroListComponent") It has no interest in what we do to get them. It doesn't care where they come from. And it certainly doesn't want to deal with a response object. + + +.callout.is-important + header HTTP GET is delayed + :marked + The `http.get` does **not send the request just yet!** This observable is + [*cold*](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/creating.md#cold-vs-hot-observables) + which means the request won't go out until something *subscribes* to the observable. + That *something* is the [HeroListComponent](#subscribe). - +a(id="error-handling") +:marked ### Always handle errors The eagle-eyed reader may have spotted our use of the `catch` operator in conjunction with a `handleError` method. @@ -243,8 +278,8 @@ a(id="HeroListComponent") In this simple app we provide rudimentary error handling in both the service and the component. We use the Observable `catch` operator on the service level. - It takes an error handling function with the failed `Response` object as the argument. - Our service handler, `errorHandler`, logs the response to the console, + It takes an error handling function with an error object as the argument. + Our service handler, `handleError`, logs the response to the console, transforms the error into a user-friendly message, and returns the message in a new, failed observable via `Observable.throw`. +makeExample('server-communication/ts/app/toh/hero.service.ts', 'error-handling', 'app/toh/hero.service.ts')(format=".") @@ -263,19 +298,7 @@ a(id="HeroListComponent") .l-sub-section :marked Want to see it fail? Reset the api endpoint in the `HeroService` to a bad value. Remember to restore it! - - -:marked - ### Peek at results in the console - During development we're often curious about the data returned by the server. - Logging to console without disrupting the flow would be nice. - - The Observable `do` operator is perfect for the job. - It passes the input through to the output while we do something with a useful side-effect such as writing to console. - Slip it into the pipeline between `map` and `catch` like this. -+makeExample('server-communication/ts/app/toh/hero.service.ts', 'http-get', 'app/toh/hero.service.ts')(format=".") -:marked - Remember to comment it out before going to production! + @@ -327,7 +350,7 @@ code-example(format="." language="javascript"). :marked ### JSON results - As with `get`, we extract the data from the response with `json()` and unwrap the hero via the `data` property. + As with `getHeroes`, we [extract the data](#extract-data) from the response with `json()` and unwrap the hero via the `data` property. .alert.is-important :marked Know the shape of the data returned by the server. diff --git a/public/resources/images/devguide/server-communication/hero-list.png b/public/resources/images/devguide/server-communication/hero-list.png index db53046c71..6d2169eaab 100644 Binary files a/public/resources/images/devguide/server-communication/hero-list.png and b/public/resources/images/devguide/server-communication/hero-list.png differ