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:
parent
4d9daf3493
commit
99caf75d3d
|
@ -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 () {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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).
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue