docs(http): fix typos, clarify endpoints and in-mem web api usage

closes #850

'until you've observables a chance' -> 'until you give observables a chance'
'method adds the the search box value' -> 'method adds the search box value'

Ward piggy backed the other updates.
This commit is contained in:
Andreas Wissel 2016-02-17 18:13:57 +01:00 committed by Ward Bell
parent 4d9daf3493
commit 99caf75d3d
3 changed files with 62 additions and 31 deletions

View File

@ -11,7 +11,8 @@ import {Hero} from './hero';
export class HeroService { export class HeroService {
constructor (private http: Http) {} constructor (private http: Http) {}
private _heroesUrl = 'app/heroes'; // URL to web api
private _heroesUrl = 'app/heroes.json';
// #docregion methods // #docregion methods
getHeroes () { getHeroes () {

View File

@ -15,9 +15,19 @@ import {Observable} from 'rxjs/Observable';
@Injectable() @Injectable()
export class HeroService { export class HeroService {
constructor (private http: Http) {} constructor (private http: Http) {}
// #enddocregion
// #enddocregion v1
/*
// #docregion endpoint-json
private _heroesUrl = 'app/heroes.json'; // URL to JSON file
// #enddocregion endpoint-json
*/
// #docregion
// #docregion v1
// #docregion endpoint // #docregion endpoint
private _heroesUrl = 'app/heroes'; private _heroesUrl = 'app/heroes'; // URL to web api
// #enddocregion endpoint // #enddocregion endpoint
// #docregion methods // #docregion methods

View File

@ -84,11 +84,11 @@ figure.image-display
Below the button is an optional error message. Below the button is an optional error message.
### The `HeroListComponent` class ### The *HeroListComponent* class
We [inject](dependency-injection.html) the `HeroService` into the constructor. 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`. 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!**. Notice that the component **does not talk to the server directly!**
The component doesn't know or care how we get the data. 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). 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*.
@ -132,9 +132,15 @@ figure.image-display
Look closely at how we call `http.get` 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-v1', 'app/toh/hero.service.ts (http.get)')(format=".")
:marked :marked
We pass the resource URL to `get` and it calls the server which returns We pass the resource URL to `get` and it calls the server which should return heroes.
data from the `heroes.json` file. .l-sub-section
:marked
It *will* return heroes once we've set up the [in-memory web api](in-mem-web-api)
described in the appendix below.
Alternatively, we can (temporarily) target a JSON file by changing the endpoint URL:
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'endpoint-json')(format=".")
:marked
<a id="rxjs"></a> <a id="rxjs"></a>
The return value may surprise us. Many of us would expect a The return value may surprise us. Many of us would expect a
[promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). [promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
@ -199,13 +205,17 @@ figure.image-display
The Angular HTTP client follows the ES2015 specification for the The Angular HTTP client follows the ES2015 specification for the
[response object](https://fetch.spec.whatwg.org/#response-class) returned by the `Fetch` function. [response object](https://fetch.spec.whatwg.org/#response-class) returned by the `Fetch` function.
That spec defines a `json()` method that parses the response body into a JavaScript object. That spec defines a `json()` method that parses the response body into a JavaScript object.
.l-sub-section
We must also know the shape of the data returned by the server API. :marked
We shouldn't expect `json()` to return the heroes array directly. We shouldn't expect `json()` to return the heroes array directly.
The server we're calling always wraps JSON results in an object with a `data` The server we're calling always wraps JSON results in an object with a `data`
property. We have to unwrap it to get the heroes. property. We have to unwrap it to get the heroes.
This is conventional web api behavior, driven by This is conventional web api behavior, driven by
[security concerns](https://www.owasp.org/index.php/OWASP_AJAX_Security_Guidelines#Always_return_JSON_with_an_Object_on_the_outside). [security concerns](https://www.owasp.org/index.php/OWASP_AJAX_Security_Guidelines#Always_return_JSON_with_an_Object_on_the_outside).
.alert.is-important
:marked
Make no assumptions about the server API.
Not all servers return an object with a `data` property.
:marked :marked
### Do not return the response object ### Do not return the response object
Our `getHeroes()` could have returned the `Observable<Response>`. Our `getHeroes()` could have returned the `Observable<Response>`.
@ -249,9 +259,7 @@ figure.image-display
.l-sub-section .l-sub-section
:marked :marked
Try messing with the value of the api endpoint in the `HeroService` and watch it fail Want to see it fail? Reset the api endpoint in the `HeroService` to a bad value. Remember to restore it!
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'endpoint', 'app/toh/hero.service.ts (endpoint)')
<a id="do"></a> <a id="do"></a>
<a id="console-log"></a> <a id="console-log"></a>
:marked :marked
@ -317,10 +325,10 @@ code-example(format="." language="javascript").
:marked :marked
### JSON results ### JSON results
As with `get`, we extract the data from the response with `json()` and unwrap the hero via the `data` property. As with `get`, we extract the data from the response with `json()` and unwrap the hero via the `data` property.
.l-sub-section .alert.is-important
:marked :marked
*Reminder*: we must know the shape of the data returned by the server. Know the shape of the data returned by the server.
The sample web api returns the new hero wrapped in an object with a `data` property. *This* web api returns the new hero wrapped in an object with a `data` property.
A different api might just return the hero in which case we'd omit the `data` de-reference. A different api might just return the hero in which case we'd omit the `data` de-reference.
:marked :marked
Back in the `HeroListComponent`, we see that *its* `addHero` method subscribes to the observable returned by the *service's* `addHero` method. Back in the `HeroListComponent`, we see that *its* `addHero` method subscribes to the observable returned by the *service's* `addHero` method.
@ -337,7 +345,7 @@ code-example(format="." language="javascript").
.l-sub-section .l-sub-section
:marked :marked
While promises may be more familiar, observables have many advantages. While promises may be more familiar, observables have many advantages.
Don't rush to promises until you've observables a chance. Don't rush to promises until you give observables a chance.
:marked :marked
Let's rewrite the `HeroService` using promises , highlighting just the parts that are different. Let's rewrite the `HeroService` using promises , highlighting just the parts that are different.
+makeTabs( +makeTabs(
@ -521,7 +529,7 @@ figure.image-display
+makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'import-subject') +makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'import-subject')
:marked :marked
Each search term is a string, so we create a new `Subject` of type `string` called `_searchTermStream`. Each search term is a string, so we create a new `Subject` of type `string` called `_searchTermStream`.
After every keystroke, the `search` method adds the the search box value to that stream After every keystroke, the `search` method adds the search box value to that stream
via the subject's `next` method. via the subject's `next` method.
+makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'subject')(format='.') +makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'subject')(format='.')
:marked :marked
@ -560,36 +568,48 @@ figure.image-display
to mitigate the [security risk](http://stackoverflow.com/questions/3503102/what-are-top-level-json-arrays-and-why-are-they-a-security-risk) to mitigate the [security risk](http://stackoverflow.com/questions/3503102/what-are-top-level-json-arrays-and-why-are-they-a-security-risk)
posed by top-level JSON arrays. posed by top-level JSON arrays.
:marked :marked
Our sample saves data. We can't save to a JSON file. We'll need a server ... or a server simulation. We'd set the endpoint to the JSON file like this:
This sample uses an in-memory web api simulator that we first installed with `npm`: +makeExample('server-communication/ts/app/toh/hero.service.ts', 'endpoint-json')(format=".")
code-example. :marked
The *get heroes* scenario would work.
But we want to *save* data too. We can't save changes to a JSON file. We need a web api server.
We didn't want the hassle of setting up and maintaining a real server for this chapter.
So we turned to an *in-memory web api simulator* instead.
You too can use it in your own development while waiting for a real server to arrive.
First, install it with `npm`:
code-example(language="bash").
npm install a2-in-memory-web-api --save npm install a2-in-memory-web-api --save
:marked :marked
and then loaded it in our html below the angular script: Then load the script in the `index.html` below angular:
+makeExample('server-communication/ts/index.html', 'in-mem-web-api', 'index.html')(format=".") +makeExample('server-communication/ts/index.html', 'in-mem-web-api', 'index.html')(format=".")
:marked :marked
The in-memory web api gets its data from a class with a `createDb()` method that returns The *in-memory web api* gets its data from a class with a `createDb()` method that returns
a "database" object whose keys are collection names ("heroes") a "database" object whose keys are collection names ("heroes")
and whose values are arrays of objects in those collections. and whose values are arrays of objects in those collections.
Here's the class we created for this sample by copy-and-pasting the JSON data: Here's the class we created for this sample by copy-and-pasting the JSON data:
+makeExample('server-communication/ts/app/hero-data.ts', null, 'app/hero-data.ts')(format=".") +makeExample('server-communication/ts/app/hero-data.ts', null, 'app/hero-data.ts')(format=".")
:marked :marked
Finally, we tell Angular to direct its http requests to the in-memory web api service rather We update the `HeroService` endpoint to the location of the web api data.
than to a remote server. +makeExample('server-communication/ts/app/toh/hero.service.ts', 'endpoint')(format=".")
:marked
Finally, we tell Angular itself to direct its http requests to the *in-memory web api* rather
than externally to a remote server.
This redirection is easy in Angular because `http` delegates the client/server communication tasks This redirection is easy because Angular's `http` delegates the client/server communication tasks
to a helper service called the `XHRBackend`. to a helper service called the `XHRBackend`.
To enable our server simulation, we replace the `XHRBackend` service with To enable our server simulation, we replace the default `XHRBackend` service with
the in-memory web api *backend* using standard Angular provider registration the *in-memory web api service* using standard Angular provider registration
in the `TohComponent`. We supply the hero data to the in-memory web api in the same way at the same time. in the `TohComponent`. We initialize the *in-memory web api* with mock hero data at the same time.
Here are the pertinent details, excerpted from `TohComponent`, starting with the imports: Here are the pertinent details, excerpted from `TohComponent`, starting with the imports:
+makeExample('server-communication/ts/app/toh/toh.component.ts', 'in-mem-web-api-imports', 'toh.component.ts (web api imports)')(format=".") +makeExample('server-communication/ts/app/toh/toh.component.ts', 'in-mem-web-api-imports', 'toh.component.ts (web api imports)')(format=".")
:marked :marked
Then add these two provider definitions to the component's `providers` array in metadata: Then we add the following two provider definitions to the `providers` array in component metadata:
+makeExample('server-communication/ts/app/toh/toh.component.ts', 'in-mem-web-api-providers', 'toh.component.ts (web api providers')(format=".") +makeExample('server-communication/ts/app/toh/toh.component.ts', 'in-mem-web-api-providers', 'toh.component.ts (web api providers)')(format=".")
:marked :marked
See the full source code in the [live example](/resources/live-examples/server-communication/ts/plnkr.html). See the full source code in the [live example](/resources/live-examples/server-communication/ts/plnkr.html).