[review-pending] docs(dev guide): server-communication - new prose

New Dart prose, update Dart and Ts code

+ guide/server-communication/ts: update to docs and example code
+ guide/server-communication/dart: new prose, update example code
+ ignore all npm-debug.logs
+ make Jade ul li TOC elements more compact.
This commit is contained in:
Patrice Chalin 2016-05-13 13:32:54 -07:00 committed by Thibault Sottiaux
parent d3cad1829b
commit c1440e7eff
18 changed files with 658 additions and 600 deletions

2
.gitignore vendored
View File

@ -20,7 +20,7 @@ _.*
public/docs/xref-*.*
_zip-output
www
/npm-debug.log
npm-debug.log
npm-debug.log.*
*.plnkr.html
plnkr.html

View File

@ -1,6 +1,8 @@
// #docregion
import 'package:http/browser_client.dart';
import 'package:http_in_memory_web_api/http_in_memory_web_api.dart';
CreateDb heroData = () => {
CreateDb createDb = () => {
'heroes': [
{"id": "1", "name": "Windstorm"},
{"id": "2", "name": "Bombasto"},
@ -8,3 +10,6 @@ CreateDb heroData = () => {
{"id": "4", "name": "Tornado"}
]
};
BrowserClient HttpClientBackendServiceFactory() =>
new HttpClientInMemoryBackendService(createDb);

View File

@ -5,11 +5,10 @@ class Hero {
Hero(this.id, this.name);
factory Hero.fromJson(Map hero) {
final _id = hero['id'];
final id = _id is int ? _id : int.parse(_id);
return new Hero(id,hero['name']);
}
factory Hero.fromJson(Map<String, dynamic> hero) =>
new Hero(_toInt(hero['id']), hero['name']);
Map toJson() => {'id': id, 'name': name};
static int _toInt(id) => id is int ? id : int.parse(id);
}

View File

@ -8,22 +8,7 @@ import 'hero_service.dart';
@Component(
selector: 'hero-list',
// #docregion template
template: '''
<h3>Heroes:</h3>
<ul>
<li *ngFor="let hero of heroes">
{{hero.name}}
</li>
</ul>
New Hero:
<input #newHero />
<button (click)="addHero(newHero.value); newHero.value=''">
Add Hero
</button>
<div class="error" *ngIf="hasErrorMessage">{{errorMessage}}</div>
''',
// #enddocregion template
templateUrl: 'hero_list_component.html',
styles: const ['.error {color:red;}'])
// #docregion component
class HeroListComponent implements OnInit {
@ -33,20 +18,21 @@ class HeroListComponent implements OnInit {
HeroListComponent(this._heroService);
bool get hasErrorMessage => errorMessage != null;
Future ngOnInit() => getHeroes();
Future<Null> ngOnInit() => getHeroes();
// #docregion methods
Future getHeroes() async {
// #docregion getHeroes
Future<Null> getHeroes() async {
try {
heroes = await _heroService.getHeroes();
} catch (e) {
errorMessage = e.toString();
}
}
// #enddocregion getHeroes
Future addHero(String name) async {
// #docregion addHero
Future<Null> addHero(String name) async {
name = name.trim();
if (name.isEmpty) return;
try {
@ -55,6 +41,7 @@ class HeroListComponent implements OnInit {
errorMessage = e.toString();
}
}
// #enddocregion addHero
// #enddocregion methods
}
// #enddocregion component

View File

@ -0,0 +1,13 @@
<!-- #docregion -->
<h3>Heroes:</h3>
<ul>
<li *ngFor="let hero of heroes">
{{hero.name}}
</li>
</ul>
New hero name:
<input #newHeroName />
<button (click)="addHero(newHeroName.value); newHeroName.value=''">
Add Hero
</button>
<div class="error" *ngIf="errorMessage != null">{{errorMessage}}</div>

View File

@ -1,41 +1,74 @@
// #docplaster
// #docregion
// #docregion v1
import 'dart:async';
import 'dart:convert';
import 'package:angular2/core.dart';
// #enddocregion v1
// #docregion import-request-options
import 'package:http/browser_client.dart';
// #enddocregion import-request-options
// #docregion v1
import 'hero.dart';
import 'package:angular2/core.dart';
import 'package:http/browser_client.dart';
import 'package:http/http.dart' show Response;
@Injectable()
class HeroService {
final String _heroesUrl = 'app/heroes';
BrowserClient _http;
// #docregion endpoint, http-get
final String _heroesUrl = 'app/heroes'; // URL to web API
// #enddocregion endpoint, http-get
final BrowserClient _http;
HeroService(this._http);
// #docregion methods
// #docregion methods, error-handling, http-get
Future<List<Hero>> getHeroes() async {
try {
final response = await _http.get(_heroesUrl);
final heroes = JSON
.decode(response.body)['data']
final heroes = _extractData(response)
.map((value) => new Hero.fromJson(value))
.toList();
print(JSON.encode(heroes)); // eyeball results in the console
return heroes;
} catch (e) {
throw _handleError(e);
}
}
// #enddocregion error-handling, http-get, v1
// #docregion addhero, addhero-sig
Future<Hero> addHero(String name) async {
final headers = {'content-type': 'application/json'};
final body = JSON.encode({'name': name});
final response = await _http.post(_heroesUrl, headers: headers, body: body);
return new Hero.fromJson(JSON.decode(response.body));
// #enddocregion addhero-sig
try {
final response = await _http.post(_heroesUrl,
headers: {'Content-Type': 'application/json'},
body: JSON.encode({'name': name}));
return new Hero.fromJson(_extractData(response));
} catch (e) {
throw _handleError(e);
}
// #enddocregion methods
}
// #enddocregion addhero, v1
// #docregion extract-data
dynamic _extractData(Response res) {
if (res.statusCode < 200 || res.statusCode >= 300) {
throw new Exception('Response status: ${res.statusCode}');
}
var body = JSON.decode(res.body);
// TODO: once fixed, https://github.com/adaojunior/http-in-memory-web-api/issues/1
// Drop the `?? body` term
return body['data'] ?? body;
}
// #enddocregion extract-data
// #docregion error-handling
Exception _handleError(dynamic e) {
// In a real world app, we might use a remote logging infrastructure
print(e); // log to console instead
return new Exception('Server error; cause: $e');
}
// #enddocregion error-handling, methods
}
// #enddocregion
/*
// #docregion endpoint-json
private _heroesUrl = 'heroes.json'; // URL to JSON file
// #enddocregion endpoint-json
*/

View File

@ -1,16 +1,18 @@
// #docplaster
// #docregion
import 'package:angular2/core.dart';
import 'package:http_in_memory_web_api/http_in_memory_web_api.dart';
import 'package:http/browser_client.dart';
import 'package:server_communication/hero_data.dart';
import 'hero_list_component.dart';
import 'hero_service.dart';
@Injectable()
HttpClientInMemoryBackendService HttpClientInMemoryBackendServiceFactory() =>
new HttpClientInMemoryBackendService(heroData); // in-mem server
// #enddocregion
// #docregion in-mem-web-api
/* ... */
import 'package:server_communication/hero_data.dart';
// #docregion
@Component(
// #enddocregion in-mem-web-api
selector: 'my-toh',
// #docregion template
template: '''
@ -18,18 +20,17 @@ HttpClientInMemoryBackendService HttpClientInMemoryBackendServiceFactory() =>
<hero-list></hero-list>
''',
// #enddocregion template
directives: const [HeroListComponent],
// #enddocregion
// #docregion in-mem-web-api
/* ... */
// #docregion
providers: const [
HeroService,
// #enddocregion
//#docregion in-mem-web-api-providers
// in-memory web api providers
const Provider(BrowserClient,
useFactory: HttpClientInMemoryBackendServiceFactory)
//#enddocregion in-mem-web-api-providers
useFactory: HttpClientBackendServiceFactory)
// #docregion
],
directives: const [
HeroListComponent
])
class TohComponent {}
// #enddocregion

View File

@ -1,10 +1,12 @@
<!DOCTYPE html>
<!-- #docregion -->
<html>
<head>
<title>Angular 2 Http Demo</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">
<script defer src="main.dart" type="application/dart"></script>
<script defer src="packages/browser/dart.js"></script>
</head>
@ -12,7 +14,7 @@
<body>
<my-toh>ToH Loading...</my-toh>
<my-wiki>Wiki Loading...</my-wiki>
<my-wiki-smart>WikiSmart loading...</my-wiki-smart>
<my-wiki-smart>WikiSmart Loading...</my-wiki-smart>
</body>
</html>

View File

@ -10,12 +10,12 @@ import { HTTP_PROVIDERS } from '@angular/http';
import 'rxjs/Rx';
// #enddocregion import-rxjs
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);
// #docregion http-providers
bootstrap(TohComponent, [HTTP_PROVIDERS]);
// #enddocregion http-providers
bootstrap(WikiComponent);
bootstrap(WikiSmartComponent);

View File

@ -5,9 +5,9 @@
{{hero.name}}
</li>
</ul>
New Hero:
<input #newHero />
<button (click)="addHero(newHero.value); newHero.value=''">
New hero name:
<input #newHeroName />
<button (click)="addHero(newHeroName.value); newHeroName.value=''">
Add Hero
</button>
<div class="error" *ngIf="errorMessage">{{errorMessage}}</div>

View File

@ -1,5 +1,4 @@
// #docplaster
// #docregion
// #docregion v1
import { Injectable } from '@angular/core';
@ -16,52 +15,36 @@ import { Observable } from 'rxjs/Observable';
@Injectable()
export class HeroService {
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
private heroesUrl = 'app/heroes'; // URL to web api
private heroesUrl = 'app/heroes'; // URL to web API
// #enddocregion endpoint
// #docregion methods
// #docregion error-handling, http-get
// #docregion methods, error-handling, http-get
getHeroes (): Observable<Hero[]> {
return this.http.get(this.heroesUrl)
.map(this.extractData)
.catch(this.handleError);
}
// #enddocregion error-handling, http-get
// #enddocregion v1
// #enddocregion error-handling, http-get, v1
// #docregion addhero
// #docregion addhero, addhero-sig
addHero (name: string): Observable<Hero> {
// #enddocregion addhero-sig
let body = JSON.stringify({ name });
// #docregion headers
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this.http.post(this.heroesUrl, body, options)
// #enddocregion headers
.map(this.extractData)
.catch(this.handleError);
}
// #enddocregion addhero
// #docregion v1
// #docregion extract-data
// #docregion v1, extract-data
private extractData(res: Response) {
if (res.status < 200 || res.status >= 300) {
throw new Error('Bad response status: ' + res.status);
throw new Error('Response status: ' + res.status);
}
let body = res.json();
return body.data || { };
@ -70,12 +53,17 @@ export class HeroService {
// #docregion error-handling
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';
console.error(errMsg); // log to console instead
return Observable.throw(errMsg);
}
// #enddocregion error-handling
// #enddocregion methods
// #enddocregion error-handling, methods
}
// #enddocregion
/*
// #docregion endpoint-json
private heroesUrl = 'app/heroes.json'; // URL to JSON file
// #enddocregion endpoint-json
*/

View File

@ -1,30 +0,0 @@
// ToH Promise Version
console.log ('Promise version');
import { Component } from '@angular/core';
import { HTTP_PROVIDERS } from '@angular/http';
import { provide } from '@angular/core';
import { XHRBackend } from '@angular/http';
import { InMemoryBackendService,
SEED_DATA } from 'angular2-in-memory-web-api/core';
import { HeroData } from '../hero-data';
import { HeroListComponent } from './hero-list.component.1';
import { HeroService } from './hero.service.1';
@Component({
selector: 'my-toh',
template: `
<h1>Tour of Heroes</h1>
<hero-list></hero-list>
`,
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 { }

View File

@ -1,44 +0,0 @@
// #docplaster
// #docregion
import { Component } from '@angular/core';
import { HTTP_PROVIDERS } from '@angular/http';
import { HeroListComponent } from './hero-list.component';
import { HeroService } from './hero.service';
// #enddocregion
// #docregion in-mem-web-api-imports
import { provide } from '@angular/core';
import { XHRBackend } from '@angular/http';
// in-memory web api imports
import { InMemoryBackendService,
SEED_DATA } from 'angular2-in-memory-web-api/core';
import { HeroData } from '../hero-data';
// #enddocregion in-mem-web-api-imports
// #docregion
@Component({
selector: 'my-toh',
// #docregion template
template: `
<h1>Tour of Heroes</h1>
<hero-list></hero-list>
`,
// #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

View File

@ -1,5 +1,4 @@
// #docplaster
// #docregion
import { Component } from '@angular/core';
import { HTTP_PROVIDERS } from '@angular/http';

View File

@ -1,5 +1,6 @@
// #docplaster
// #docregion
// #docregion just-get-heroes
import 'dart:async';
import 'package:angular2/core.dart';
@ -12,12 +13,14 @@ class HeroService {
// #docregion get-heroes
Future<List<Hero>> getHeroes() async => mockHeroes;
// #enddocregion get-heroes
// #enddocregion just-get-heroes
// See the "Take it slow" appendix
// #docregion get-heroes-slowly
Future<List<Hero>> getHeroesSlowly() {
return new Future.delayed(const Duration(seconds: 2), () => mockHeroes);
}
// #enddocregion get-heroes-slowly
// #docregion just-get-heroes
}
// #enddocregion just-get-heroes
// #enddocregion

View File

@ -1,11 +1,82 @@
extends ../../../ts/latest/guide/server-communication.jade
block includes
include ../_util-fns
block http-providers
//- TODO(chalin): mention the Angular transformer resolved_identifiers.
//- Maybe not yet at this point in the chapter.
block getheroes-and-addhero
:marked
We're working on the Dart version of this chapter.
In the meantime, please see these resources:
The hero service `getHeroes()` and `addHero()` asynchronous methods return the
[`Future`](https://api.dartlang.org/stable/1.16.0/dart-async/Future-class.html)
values of the current hero list and the newly added hero,
respectively. The hero list component methods of the same name specifying
the actions to be taken when the asynchronous method calls succeed or fail.
* [Http Client](/docs/ts/latest/guide/server-communication.html):
The TypeScript version of this chapter.
For more information about `Future`s, consult any one
of the [articles](https://www.dartlang.org/articles/) on asynchronous
programming in Dart, or the tutorial on
[_Asynchronous Programming: Futures_](https://www.dartlang.org/docs/tutorials/futures/).
* [Dart source code](https://github.com/angular/angular.io/tree/master/public/docs/_examples/server-communication/dart):
A preliminary version of the example code that will appear in this chapter.
block http-client-service
:marked
The imported `BrowserClient` client service gets
[injected](dependency-injection.html) into the `HeroService` constructor.
Note that `BrowserClient` is not part of the Angular core.
It's an optional service provided by the Dart
[`http` package](https://pub.dartlang.org/packages/http).
block rxjs
//- N/A
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 test for this condition and throw an
exception when detected.
block parse-json
:marked
The response data are in JSON string form.
We must parse that string into JavaScript objects which we do by calling
the `JSON.decode()` method from the `dart:convert` library.
block error-handling
//- TODO: describe `_handleError`?
block hlc-error-handling
:marked
Back in the `HeroListComponent`, we wrapped our call to
`#{_priv}heroService.getHeroes()` in a `try` clause. When an exception is
caught, the `errorMessage` variable &mdash; which we've bound conditionally in the
template &mdash; gets assigned to.
block hero-list-comp-add-hero
:marked
Back in the `HeroListComponent`, we see that *its* `addHero()`
awaits for the *service's* asynchronous `addHero()` to return, and when it does,
the new hero is added to the `heroes` list for presentation to the user.
+makeExample('server-communication/ts/app/toh/hero-list.component.ts', 'addHero', 'app/toh/hero-list.component.ts (addHero)')(format=".")
block promises
//- N/A
block wikipedia-jsonp+
:marked
Wikipedia offers both `CORS` and `JSONP` search APIs.
.alert.is-important
:marked
The remaining content of this section is coming soon.
In the meantime, consult the
[example sources](https://github.com/angular-examples/server-communication)
to see how to access Wikipedia via its `JSONP` API.
block redirect-to-web-api
:marked
To achieve this, we have Angular inject an in-memory web API server
instance as a provider for the `BrowserClient`. This is possible because
the in-memory web API server class extends `BrowserClient`. Here are the
pertinent details, excerpt from `TohComponent`:
+makeExample('server-communication/ts/app/toh/toh.component.ts', 'in-mem-web-api', 'app/toh.component.ts (excerpt)')(format=".")

View File

@ -1,4 +1,4 @@
block includes
include ../_util-fns
:marked
[HTTP](https://tools.ietf.org/html/rfc2616) is the primary protocol for browser/server communication.
@ -15,33 +15,36 @@ include ../_util-fns
The Angular HTTP client library simplifies application programming of the **XHR** and **JSONP** APIs
as we'll learn in this chapter covering:
[Http client sample overview](#http-client)<br>
[Fetch data with http.get](#fetch-data)<br>
[RxJS Observable of HTTP Responses](#rxjs)<br>
[Enabling RxJS Operators](#enable-rxjs-operators)<br>
[Extract JSON data with RxJS map](#map)<br>
[Error handling](#error-handling)<br>
[Send data to the server](#update)<br>
[Add headers](#headers)<br>
[Promises instead of observables](#promises)<br>
[JSONP](#jsonp)<br>
[Set query string parameters](#search-parameters)<br>
[Debounce search term input](#more-observables)<br>
[Appendix: the in-memory web api service](#in-mem-web-api)<br>
ul
li #[a(href="#http-client") Http client sample overview]
li #[a(href="#fetch-data") Fetch data with http.get]
+ifDocsFor('ts')
li #[a(href="#rxjs") RxJS Observable of HTTP Responses]
li #[a(href="#enable-rxjs-operators") Enabling RxJS Operators]
li #[a(href="#extract-data") Extract JSON data]
li #[a(href="#error-handling") Error handling]
li #[a(href="#update") Send data to the server]
+ifDocsFor('ts')
li #[a(href="#promises") Promises instead of observables]
li #[a(href="#cross-origin-requests") Cross-origin requests: Wikipedia example]
+ifDocsFor('ts')
ul
li #[a(href="#search-parameters") Set query string parameters]
li #[a(href="#more-observables") Debounce search term input]
li #[a(href="#in-mem-web-api") Appendix: the in-memory web api service]
p.
We illustrate these topics with code that you can
[run live in a browser](/resources/live-examples/server-communication/ts/plnkr.html).
#[+liveExampleLink2('run live in a browser', 'server-communication')].
.l-main-section
a#http-client
:marked
<a id="http-client"></a>
## The *Http* Client Demo
We use the Angular `Http` client to communicate via `XMLHttpRequest (XHR)`.
We'll demonstrate with a mini-version of the [tutorial](../tutorial)'s "Tour of Heroes" (ToH) application.
This version gets some heroes from the server, displays them in a list, lets us add new heroes, and save them to the server.
This version gets some heroes from the server, displays them in a list, lets us add new heroes, and saves them to the server.
It works like this.
figure.image-display
@ -56,7 +59,8 @@ figure.image-display
We're making a point about application structure that is easier to justify when the app grows.
:marked
Here is the `TohComponent` shell:
+makeExample('server-communication/ts/app/toh/toh.component.ts', '', 'app/toh.component.ts')
+makeExample('server-communication/ts/app/toh/toh.component.ts', '', 'app/toh/toh.component.ts')
block http-providers
:marked
As usual, we import the symbols we need. The newcomer is `HTTP_PROVIDERS`,
an array of service providers from the Angular HTTP library.
@ -75,20 +79,20 @@ figure.image-display
:marked
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)')
+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.
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 [template reference variable](template-syntax.html#ref-vars), `newHero`, to access the
We use a [template reference variable](template-syntax.html#ref-vars), `newHeroName`, to access the
value of the input box in the `(click)` event binding.
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 a (hidden) area for an error message.
Below the button is an area for an error message.
a(id="oninit")
a(id="HeroListComponent")
@ -115,12 +119,14 @@ a(id="HeroListComponent")
This is a "best practice".
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.
block getheroes-and-addhero
:marked
The service `get` and `addHero` methods return an `Observable` of HTTP hero data.
The service's `getHeroes()` 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.
:marked
With our basic intuitions about the component squared away, we can turn to development of the backend data source
and the client-side `HeroService` that talks to it.
@ -130,21 +136,22 @@ a(id="HeroListComponent")
returning mock heroes in a service like this one:
+makeExample('toh-4/ts/app/hero.service.ts', 'just-get-heroes')(format=".")
:marked
In this chapter, we get the heroes from the server using Angular's own HTTP Client service.
In this chapter, we get the heroes from the server using a (browser-based) HTTP client service.
Here's the new `HeroService`:
+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.
block http-client-service
:marked
The imported `Http` client service gets
[injected](dependency-injection.html) into the `HeroService` constructor.
.l-sub-section
:marked
`Http` is not part of the Angular core. It's an optional service in its own `@angular/http` library
that we installed with npm (see the `package.json`) and
registered for module loading by SystemJS (see `systemjs.config.js`)
:marked
Look closely at how we call `http.get`
Look closely at how we call `#{_priv}http.get`
+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.
@ -152,9 +159,10 @@ a(id="HeroListComponent")
: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=".")
block rxjs
:marked
<a id="rxjs"></a>
The return value may surprise us. Many of us would expect a
@ -174,7 +182,6 @@ a(id="HeroListComponent")
All of our Developer Guide samples have installed the RxJS npm package and loaded via `system.js`
because observables are used widely in Angular applications.
We certainly need it now when working with the HTTP client.
And we must take a critical extra step to make RxJS observables usable.
@ -198,45 +205,54 @@ a(id="HeroListComponent")
:
+makeExample('server-communication/ts/app/main.ts', 'import-rxjs', 'app/main.ts (import rxjs)')(format=".")
a(id="map")
a(id="extract-data")
a#extract-data
:marked
### 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=".")
Remember that our `getHeroes()` method mapped the `#{_priv}http.get` response object to heroes with an `#{_priv}extractData` helper method:
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'extract-data', 'app/toh/hero.service.ts (excerpt)')(format=".")
:marked
The `response` object does not hold our data in a form we can use directly.
To make it useful in our application we must
* check for a bad response
* 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
#### 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_.
#### 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 observable error only if `http` failed to operate (e.g., it errored internally).
We'd have an exception only if `#{_priv}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,
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
block parse-json
:marked
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.
The Angular HTTP client follows the ES2015 specification for the
[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.
.l-sub-section
:marked
We shouldn't expect `json()` to return the heroes array directly.
We shouldn't expect the decoded JSON to be the heroes #{_array} directly.
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.
This is conventional web api behavior, driven by
@ -247,50 +263,50 @@ a(id="extract-data")
Not all servers return an object with a `data` property.
:marked
### Do not return the response object
Our `getHeroes()` could have returned the `Observable<Response>`.
Bad idea! The point of a data service is to hide the server interaction details from consumers.
Our `getHeroes()` could have returned the HTTP response. Bad idea!
The point of a data service is to hide the server interaction details from consumers.
The component that calls the `HeroService` wants heroes.
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.
+ifDocsFor('ts')
.callout.is-important
header HTTP GET is delayed
:marked
The `http.get` does **not send the request just yet!** This observable is
The `#{_priv}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")
a#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.
We haven't discussed so far how that actually works.
Whenever we deal with I/O we must be prepared for something to go wrong as it surely will.
We should catch errors in the `HeroService` and do something with them.
We may also pass an error message back to the component for presentation to the user
but only if we can say something the user can understand and act upon.
In this simple app we provide rudimentary error handling in both the service and the component.
block error-handling
:marked
The eagle-eyed reader may have spotted our use of the `catch` operator in conjunction with a `handleError` method.
We haven't discussed so far how that actually works.
We use the Observable `catch` operator on the service level.
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=".")
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'error-handling', 'app/toh/hero.service.ts (excerpt)')(format=".")
<a id="subscribe"></a>
<a id="hero-list-component"></a>
.l-main-section
a#subscribe
a#hero-list-component
h4 #[b HeroListComponent] error handling
block hlc-error-handling
:marked
## Subscribe in the *HeroListComponent*
Back in the `HeroListComponent`, where we called `heroService.get`,
Back in the `HeroListComponent`, where we called `#{_priv}heroService.getHeroes()`,
we supply the `subscribe` function with a second function to handle the error message.
It sets an `errorMessage` variable which we've bound conditionally in the template.
@ -307,13 +323,14 @@ a(id="error-handling")
:marked
## Send data to the server
So far we've seen how to retrieve data from a remote location using Angular's built-in `Http` service.
So far we've seen how to retrieve data from a remote location using an HTTP service.
Let's add the ability to create new heroes and save them in the backend.
We'll create an easy method for the `HeroListComponent` to call, an `addHero` method that takes
just the name of a new hero and returns an observable holding the newly-saved hero:
code-example(format="." language="javascript").
addHero (name: string) : Observable&lt;Hero>
We'll create an easy method for the `HeroListComponent` to call, an `addHero()` method that takes
just the name of a new hero:
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'addhero-sig')(format=".")
:marked
To implement it, we need to know some details about the server's api for creating heroes.
@ -331,38 +348,47 @@ code-example(format="." language="javascript").
of the new hero including its generated id. The hero arrives tucked inside a response object
with its own `data` property.
Now that we know how the API works, we implement `addHero`like this:
Now that we know how the API works, we implement `addHero()`like this:
+ifDocsFor('ts')
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'import-request-options', 'app/toh/hero.service.ts (additional imports)')(format=".")
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'addhero', 'app/toh/hero.service.ts (addHero)')(format=".")
:marked
The second *body* parameter of the `post` method requires a JSON ***string***
so we have to `JSON.stringify` the hero content before sending.
.l-sub-section
:marked
We may be able to skip the `stringify` step in the near future.
<a id="headers"></a>
:marked
### Headers
The server requires a `Content-Type` header for the body of the POST.
The `Content-Type` header allows us to inform the server that the body will represent JSON.
+ifDocsFor('ts')
:marked
[Headers](../api/http/Headers-class.html) are one of the [RequestOptions](../api/http/RequestOptions-class.html).
Compose the options object and pass it in as the *third* parameter of the `post` method.
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'headers', 'app/toh/hero.service.ts (headers)')(format=".")
Compose the options object and pass it in as the *third* parameter of the `post` method, as shown above.
:marked
### Body
Despite the content type being specified as JSON, the POST body must actually be a *string*.
Hence, we explicitly encode the JSON hero content before passing it in as the body argument.
+ifDocsFor('ts')
.l-sub-section
:marked
We may be able to skip the `JSON.stringify` step in the near future.
:marked
### JSON results
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
As with `getHeroes()`, we [extract the data](#extract-data) from the response using the
`#{_priv}extractData()` helper.
block hero-list-comp-add-hero
:marked
Know the shape of the data returned by the server.
*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.
: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.
When the data arrive it pushes the new hero object into its `heroes` array for presentation to the user.
+makeExample('server-communication/ts/app/toh/hero-list.component.ts', 'addHero', 'app/toh/hero-list.component.ts (addHero)')(format=".")
<a id="promises"></a>
block promises
a#promises
:marked
## Fall back to Promises
@ -410,11 +436,12 @@ code-example(format="." language="javascript").
The `Subscription` object has a different purpose, signified by its primary method, `unsubscribe`.
Learn more about observables to understand the implications and consequences of subscriptions.
<a id="jsonp"></a>
:marked
## Get data with `JSONP`
We just learned how to make `XMLHttpRequests` using Angulars built-in `Http` service.
a#cross-origin-requests
:marked
## Cross-origin requests: Wikipedia example
We just learned how to make `XMLHttpRequests` using Angular's built-in `Http` service.
This is the most common approach for server communication.
It doesn't work in all scenarios.
@ -437,10 +464,14 @@ code-example(format="." language="javascript").
:marked
### Search wikipedia
Wikipedia offers a `JSONP` search api. Let's build a simple search that shows suggestions from wikipedia as we type in a text box.
Let's build a simple search that shows suggestions from wikipedia as we type in a text box.
figure.image-display
img(src='/resources/images/devguide/server-communication/wiki-1.gif' alt="Wikipedia search app (v.1)" width="250")
block wikipedia-jsonp+
:marked
Wikipedia offers both `CORS` and `JSONP` search APIs, let's use the latter for this example.
The Angular `Jsonp` service both extends the `Http` service for JSONP and restricts us to `GET` requests.
All other HTTP methods throw an error because JSONP is a read-only facility.
@ -452,7 +483,6 @@ figure.image-display
We register that service with `JSONP_PROVIDERS` in the [component below](#wikicomponent) that calls our `WikipediaService`.
<a id="query-parameters"></a>
<a id="search-parameters"></a>
:marked
### Search parameters
The [Wikipedia 'opensearch' API](https://www.mediawiki.org/wiki/API:Opensearch)
@ -582,7 +612,7 @@ figure.image-display
The displayed list of search results stays in sync with the user's sequence of search terms.
<a id="in-mem-web-api"></a>
a#in-mem-web-api
.l-main-section
:marked
## Appendix: Tour of Heroes in-memory server
@ -597,45 +627,46 @@ figure.image-display
:marked
We'd set the endpoint to the JSON file like this:
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'endpoint-json')(format=".")
- var _a_ca_class_with = _docsFor === 'ts' ? 'a custom application class with' : ''
: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.
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.
So we turned to an *in-memory web API simulator* instead.
.l-sub-section
:marked
The *in-memory web api* is not part of the Angular core.
The in-memory web api is not part of the Angular core.
It's an optional service in its own `angular2-in-memory-web-api` library
that we installed with npm (see the `package.json`) and
that we installed with npm (see `package.json`) and
registered for module loading by SystemJS (see `systemjs.config.js`)
:marked
The *in-memory web api* gets its data from a custom, application class with a `createDb()` method that returns
a "database" object whose keys are collection names ("heroes")
and whose values are arrays of objects in those collections.
The in-memory web API gets its data from #{_a_ca_class_with} a `createDb()`
method that returns a map whose keys are collection names and whose values
are #{_array}s of objects in those collections.
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=".")
:marked
We update the `HeroService` endpoint to the location of the web api data.
Ensure that the `HeroService` endpoint refers to the web API:
+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 because Angular's `http` service delegates the client/server communication tasks
Finally, we need to redirect client HTTP requests to the in-memory web API.
block redirect-to-web-api
:marked
This redirection is easy to configure because Angular's `http` service delegates the client/server communication tasks
to a helper service called the `XHRBackend`.
To enable our server simulation, we replace the default `XHRBackend` service with
the *in-memory web api service* using standard Angular provider registration
in the `TohComponent`. We initialize the *in-memory web api* with mock hero data at the same time.
the in-memory web API service using standard Angular provider registration
in `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:
+makeExample('server-communication/ts/app/toh/toh.component.ts', 'in-mem-web-api-imports', 'toh.component.ts (web api imports)')(format=".")
Here are the pertinent details, excerpt 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=".")
:marked
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=".")
:marked
See the full source code in the [live example](/resources/live-examples/server-communication/ts/plnkr.html).
+makeExample('server-communication/ts/app/toh/toh.component.ts', 'in-mem-web-api-providers', 'toh.component.ts (web API providers)')(format=".")
p See the full source code in the #[+liveExampleLink2('live example', 'server-communication')].