[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:
parent
d3cad1829b
commit
c1440e7eff
2
.gitignore
vendored
2
.gitignore
vendored
@ -20,7 +20,7 @@ _.*
|
|||||||
public/docs/xref-*.*
|
public/docs/xref-*.*
|
||||||
_zip-output
|
_zip-output
|
||||||
www
|
www
|
||||||
/npm-debug.log
|
npm-debug.log
|
||||||
npm-debug.log.*
|
npm-debug.log.*
|
||||||
*.plnkr.html
|
*.plnkr.html
|
||||||
plnkr.html
|
plnkr.html
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
// #docregion
|
||||||
|
import 'package:http/browser_client.dart';
|
||||||
import 'package:http_in_memory_web_api/http_in_memory_web_api.dart';
|
import 'package:http_in_memory_web_api/http_in_memory_web_api.dart';
|
||||||
|
|
||||||
CreateDb heroData = () => {
|
CreateDb createDb = () => {
|
||||||
'heroes': [
|
'heroes': [
|
||||||
{"id": "1", "name": "Windstorm"},
|
{"id": "1", "name": "Windstorm"},
|
||||||
{"id": "2", "name": "Bombasto"},
|
{"id": "2", "name": "Bombasto"},
|
||||||
@ -8,3 +10,6 @@ CreateDb heroData = () => {
|
|||||||
{"id": "4", "name": "Tornado"}
|
{"id": "4", "name": "Tornado"}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
BrowserClient HttpClientBackendServiceFactory() =>
|
||||||
|
new HttpClientInMemoryBackendService(createDb);
|
||||||
|
@ -5,11 +5,10 @@ class Hero {
|
|||||||
|
|
||||||
Hero(this.id, this.name);
|
Hero(this.id, this.name);
|
||||||
|
|
||||||
factory Hero.fromJson(Map hero) {
|
factory Hero.fromJson(Map<String, dynamic> hero) =>
|
||||||
final _id = hero['id'];
|
new Hero(_toInt(hero['id']), hero['name']);
|
||||||
final id = _id is int ? _id : int.parse(_id);
|
|
||||||
return new Hero(id,hero['name']);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map toJson() => {'id': id, 'name': name};
|
Map toJson() => {'id': id, 'name': name};
|
||||||
|
|
||||||
|
static int _toInt(id) => id is int ? id : int.parse(id);
|
||||||
}
|
}
|
||||||
|
@ -8,22 +8,7 @@ import 'hero_service.dart';
|
|||||||
|
|
||||||
@Component(
|
@Component(
|
||||||
selector: 'hero-list',
|
selector: 'hero-list',
|
||||||
// #docregion template
|
templateUrl: 'hero_list_component.html',
|
||||||
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
|
|
||||||
styles: const ['.error {color:red;}'])
|
styles: const ['.error {color:red;}'])
|
||||||
// #docregion component
|
// #docregion component
|
||||||
class HeroListComponent implements OnInit {
|
class HeroListComponent implements OnInit {
|
||||||
@ -33,20 +18,21 @@ class HeroListComponent implements OnInit {
|
|||||||
|
|
||||||
HeroListComponent(this._heroService);
|
HeroListComponent(this._heroService);
|
||||||
|
|
||||||
bool get hasErrorMessage => errorMessage != null;
|
Future<Null> ngOnInit() => getHeroes();
|
||||||
|
|
||||||
Future ngOnInit() => getHeroes();
|
|
||||||
|
|
||||||
// #docregion methods
|
// #docregion methods
|
||||||
Future getHeroes() async {
|
// #docregion getHeroes
|
||||||
|
Future<Null> getHeroes() async {
|
||||||
try {
|
try {
|
||||||
heroes = await _heroService.getHeroes();
|
heroes = await _heroService.getHeroes();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
errorMessage = e.toString();
|
errorMessage = e.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// #enddocregion getHeroes
|
||||||
|
|
||||||
Future addHero(String name) async {
|
// #docregion addHero
|
||||||
|
Future<Null> addHero(String name) async {
|
||||||
name = name.trim();
|
name = name.trim();
|
||||||
if (name.isEmpty) return;
|
if (name.isEmpty) return;
|
||||||
try {
|
try {
|
||||||
@ -55,6 +41,7 @@ class HeroListComponent implements OnInit {
|
|||||||
errorMessage = e.toString();
|
errorMessage = e.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// #enddocregion addHero
|
||||||
// #enddocregion methods
|
// #enddocregion methods
|
||||||
}
|
}
|
||||||
// #enddocregion component
|
// #enddocregion component
|
||||||
|
@ -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>
|
@ -1,41 +1,74 @@
|
|||||||
// #docplaster
|
// #docplaster
|
||||||
|
|
||||||
// #docregion
|
// #docregion
|
||||||
|
// #docregion v1
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
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 'hero.dart';
|
||||||
|
import 'package:angular2/core.dart';
|
||||||
|
import 'package:http/browser_client.dart';
|
||||||
|
import 'package:http/http.dart' show Response;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
class HeroService {
|
class HeroService {
|
||||||
final String _heroesUrl = 'app/heroes';
|
// #docregion endpoint, http-get
|
||||||
BrowserClient _http;
|
final String _heroesUrl = 'app/heroes'; // URL to web API
|
||||||
|
// #enddocregion endpoint, http-get
|
||||||
|
final BrowserClient _http;
|
||||||
|
|
||||||
HeroService(this._http);
|
HeroService(this._http);
|
||||||
|
|
||||||
// #docregion methods
|
// #docregion methods, error-handling, http-get
|
||||||
Future<List<Hero>> getHeroes() async {
|
Future<List<Hero>> getHeroes() async {
|
||||||
|
try {
|
||||||
final response = await _http.get(_heroesUrl);
|
final response = await _http.get(_heroesUrl);
|
||||||
final heroes = JSON
|
final heroes = _extractData(response)
|
||||||
.decode(response.body)['data']
|
|
||||||
.map((value) => new Hero.fromJson(value))
|
.map((value) => new Hero.fromJson(value))
|
||||||
.toList();
|
.toList();
|
||||||
print(JSON.encode(heroes)); // eyeball results in the console
|
|
||||||
return heroes;
|
return heroes;
|
||||||
|
} catch (e) {
|
||||||
|
throw _handleError(e);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// #enddocregion error-handling, http-get, v1
|
||||||
|
|
||||||
|
// #docregion addhero, addhero-sig
|
||||||
Future<Hero> addHero(String name) async {
|
Future<Hero> addHero(String name) async {
|
||||||
final headers = {'content-type': 'application/json'};
|
// #enddocregion addhero-sig
|
||||||
final body = JSON.encode({'name': name});
|
try {
|
||||||
final response = await _http.post(_heroesUrl, headers: headers, body: body);
|
final response = await _http.post(_heroesUrl,
|
||||||
return new Hero.fromJson(JSON.decode(response.body));
|
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
|
// #enddocregion
|
||||||
|
|
||||||
|
/*
|
||||||
|
// #docregion endpoint-json
|
||||||
|
private _heroesUrl = 'heroes.json'; // URL to JSON file
|
||||||
|
// #enddocregion endpoint-json
|
||||||
|
*/
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
|
// #docplaster
|
||||||
|
// #docregion
|
||||||
import 'package:angular2/core.dart';
|
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:http/browser_client.dart';
|
||||||
import 'package:server_communication/hero_data.dart';
|
|
||||||
|
|
||||||
import 'hero_list_component.dart';
|
import 'hero_list_component.dart';
|
||||||
import 'hero_service.dart';
|
import 'hero_service.dart';
|
||||||
|
// #enddocregion
|
||||||
@Injectable()
|
// #docregion in-mem-web-api
|
||||||
HttpClientInMemoryBackendService HttpClientInMemoryBackendServiceFactory() =>
|
/* ... */
|
||||||
new HttpClientInMemoryBackendService(heroData); // in-mem server
|
import 'package:server_communication/hero_data.dart';
|
||||||
|
// #docregion
|
||||||
|
|
||||||
@Component(
|
@Component(
|
||||||
|
// #enddocregion in-mem-web-api
|
||||||
selector: 'my-toh',
|
selector: 'my-toh',
|
||||||
// #docregion template
|
// #docregion template
|
||||||
template: '''
|
template: '''
|
||||||
@ -18,18 +20,17 @@ HttpClientInMemoryBackendService HttpClientInMemoryBackendServiceFactory() =>
|
|||||||
<hero-list></hero-list>
|
<hero-list></hero-list>
|
||||||
''',
|
''',
|
||||||
// #enddocregion template
|
// #enddocregion template
|
||||||
|
directives: const [HeroListComponent],
|
||||||
|
// #enddocregion
|
||||||
|
// #docregion in-mem-web-api
|
||||||
|
/* ... */
|
||||||
|
// #docregion
|
||||||
providers: const [
|
providers: const [
|
||||||
HeroService,
|
HeroService,
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
//#docregion in-mem-web-api-providers
|
|
||||||
// in-memory web api providers
|
// in-memory web api providers
|
||||||
const Provider(BrowserClient,
|
const Provider(BrowserClient,
|
||||||
useFactory: HttpClientInMemoryBackendServiceFactory)
|
useFactory: HttpClientBackendServiceFactory)
|
||||||
//#enddocregion in-mem-web-api-providers
|
|
||||||
// #docregion
|
// #docregion
|
||||||
],
|
|
||||||
directives: const [
|
|
||||||
HeroListComponent
|
|
||||||
])
|
])
|
||||||
class TohComponent {}
|
class TohComponent {}
|
||||||
// #enddocregion
|
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<!-- #docregion -->
|
<!-- #docregion -->
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>Angular 2 Http Demo</title>
|
<title>Angular 2 Http Demo</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<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="main.dart" type="application/dart"></script>
|
||||||
<script defer src="packages/browser/dart.js"></script>
|
<script defer src="packages/browser/dart.js"></script>
|
||||||
</head>
|
</head>
|
||||||
@ -12,7 +14,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<my-toh>ToH Loading...</my-toh>
|
<my-toh>ToH Loading...</my-toh>
|
||||||
<my-wiki>Wiki Loading...</my-wiki>
|
<my-wiki>Wiki Loading...</my-wiki>
|
||||||
<my-wiki-smart>WikiSmart loading...</my-wiki-smart>
|
<my-wiki-smart>WikiSmart Loading...</my-wiki-smart>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
@ -10,12 +10,12 @@ import { HTTP_PROVIDERS } from '@angular/http';
|
|||||||
import 'rxjs/Rx';
|
import 'rxjs/Rx';
|
||||||
// #enddocregion import-rxjs
|
// #enddocregion import-rxjs
|
||||||
|
|
||||||
|
import { TohComponent } from './toh/toh.component';
|
||||||
import { WikiComponent } from './wiki/wiki.component';
|
import { WikiComponent } from './wiki/wiki.component';
|
||||||
import { WikiSmartComponent } from './wiki/wiki-smart.component';
|
import { WikiSmartComponent } from './wiki/wiki-smart.component';
|
||||||
import { TohComponent } from './toh/toh.component';
|
|
||||||
|
|
||||||
bootstrap(WikiComponent);
|
|
||||||
bootstrap(WikiSmartComponent);
|
|
||||||
// #docregion http-providers
|
// #docregion http-providers
|
||||||
bootstrap(TohComponent, [HTTP_PROVIDERS]);
|
bootstrap(TohComponent, [HTTP_PROVIDERS]);
|
||||||
// #enddocregion http-providers
|
// #enddocregion http-providers
|
||||||
|
bootstrap(WikiComponent);
|
||||||
|
bootstrap(WikiSmartComponent);
|
||||||
|
@ -5,9 +5,9 @@
|
|||||||
{{hero.name}}
|
{{hero.name}}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
New Hero:
|
New hero name:
|
||||||
<input #newHero />
|
<input #newHeroName />
|
||||||
<button (click)="addHero(newHero.value); newHero.value=''">
|
<button (click)="addHero(newHeroName.value); newHeroName.value=''">
|
||||||
Add Hero
|
Add Hero
|
||||||
</button>
|
</button>
|
||||||
<div class="error" *ngIf="errorMessage">{{errorMessage}}</div>
|
<div class="error" *ngIf="errorMessage">{{errorMessage}}</div>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
// #docplaster
|
// #docplaster
|
||||||
|
|
||||||
// #docregion
|
// #docregion
|
||||||
// #docregion v1
|
// #docregion v1
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
@ -16,52 +15,36 @@ 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'; // URL to web api
|
private heroesUrl = 'app/heroes'; // URL to web API
|
||||||
// #enddocregion endpoint
|
// #enddocregion endpoint
|
||||||
|
|
||||||
// #docregion methods
|
// #docregion methods, error-handling, http-get
|
||||||
// #docregion error-handling, http-get
|
|
||||||
getHeroes (): Observable<Hero[]> {
|
getHeroes (): Observable<Hero[]> {
|
||||||
return this.http.get(this.heroesUrl)
|
return this.http.get(this.heroesUrl)
|
||||||
.map(this.extractData)
|
.map(this.extractData)
|
||||||
.catch(this.handleError);
|
.catch(this.handleError);
|
||||||
}
|
}
|
||||||
// #enddocregion error-handling, http-get
|
// #enddocregion error-handling, http-get, v1
|
||||||
// #enddocregion v1
|
|
||||||
|
|
||||||
// #docregion addhero
|
// #docregion addhero, addhero-sig
|
||||||
addHero (name: string): Observable<Hero> {
|
addHero (name: string): Observable<Hero> {
|
||||||
|
// #enddocregion addhero-sig
|
||||||
let body = JSON.stringify({ name });
|
let body = JSON.stringify({ name });
|
||||||
// #docregion headers
|
|
||||||
let headers = new Headers({ 'Content-Type': 'application/json' });
|
let headers = new Headers({ 'Content-Type': 'application/json' });
|
||||||
let options = new RequestOptions({ headers: headers });
|
let options = new RequestOptions({ headers: headers });
|
||||||
|
|
||||||
return this.http.post(this.heroesUrl, body, options)
|
return this.http.post(this.heroesUrl, body, options)
|
||||||
// #enddocregion headers
|
|
||||||
.map(this.extractData)
|
.map(this.extractData)
|
||||||
.catch(this.handleError);
|
.catch(this.handleError);
|
||||||
}
|
}
|
||||||
// #enddocregion addhero
|
// #enddocregion addhero
|
||||||
|
|
||||||
// #docregion v1
|
// #docregion v1, extract-data
|
||||||
|
|
||||||
// #docregion extract-data
|
|
||||||
private extractData(res: Response) {
|
private extractData(res: Response) {
|
||||||
if (res.status < 200 || res.status >= 300) {
|
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();
|
let body = res.json();
|
||||||
return body.data || { };
|
return body.data || { };
|
||||||
@ -70,12 +53,17 @@ export class HeroService {
|
|||||||
|
|
||||||
// #docregion error-handling
|
// #docregion error-handling
|
||||||
private handleError (error: any) {
|
private handleError (error: any) {
|
||||||
// In a real world app, we might send the error to remote logging infrastructure
|
// In a real world app, we might use a remote logging infrastructure
|
||||||
let errMsg = error.message || 'Server error';
|
let errMsg = error.message || 'Server error';
|
||||||
console.error(errMsg); // log to console instead
|
console.error(errMsg); // log to console instead
|
||||||
return Observable.throw(errMsg);
|
return Observable.throw(errMsg);
|
||||||
}
|
}
|
||||||
// #enddocregion error-handling
|
// #enddocregion error-handling, methods
|
||||||
// #enddocregion methods
|
|
||||||
}
|
}
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
|
|
||||||
|
/*
|
||||||
|
// #docregion endpoint-json
|
||||||
|
private heroesUrl = 'app/heroes.json'; // URL to JSON file
|
||||||
|
// #enddocregion endpoint-json
|
||||||
|
*/
|
||||||
|
@ -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 { }
|
|
@ -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
|
|
@ -1,5 +1,4 @@
|
|||||||
// #docplaster
|
// #docplaster
|
||||||
|
|
||||||
// #docregion
|
// #docregion
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { HTTP_PROVIDERS } from '@angular/http';
|
import { HTTP_PROVIDERS } from '@angular/http';
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
// #docplaster
|
// #docplaster
|
||||||
// #docregion
|
// #docregion
|
||||||
|
// #docregion just-get-heroes
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:angular2/core.dart';
|
import 'package:angular2/core.dart';
|
||||||
@ -12,12 +13,14 @@ class HeroService {
|
|||||||
// #docregion get-heroes
|
// #docregion get-heroes
|
||||||
Future<List<Hero>> getHeroes() async => mockHeroes;
|
Future<List<Hero>> getHeroes() async => mockHeroes;
|
||||||
// #enddocregion get-heroes
|
// #enddocregion get-heroes
|
||||||
|
// #enddocregion just-get-heroes
|
||||||
// See the "Take it slow" appendix
|
// See the "Take it slow" appendix
|
||||||
// #docregion get-heroes-slowly
|
// #docregion get-heroes-slowly
|
||||||
Future<List<Hero>> getHeroesSlowly() {
|
Future<List<Hero>> getHeroesSlowly() {
|
||||||
return new Future.delayed(const Duration(seconds: 2), () => mockHeroes);
|
return new Future.delayed(const Duration(seconds: 2), () => mockHeroes);
|
||||||
}
|
}
|
||||||
// #enddocregion get-heroes-slowly
|
// #enddocregion get-heroes-slowly
|
||||||
|
// #docregion just-get-heroes
|
||||||
}
|
}
|
||||||
|
// #enddocregion just-get-heroes
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
|
@ -1,11 +1,82 @@
|
|||||||
|
extends ../../../ts/latest/guide/server-communication.jade
|
||||||
|
|
||||||
|
block includes
|
||||||
include ../_util-fns
|
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
|
:marked
|
||||||
We're working on the Dart version of this chapter.
|
The hero service `getHeroes()` and `addHero()` asynchronous methods return the
|
||||||
In the meantime, please see these resources:
|
[`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):
|
For more information about `Future`s, consult any one
|
||||||
The TypeScript version of this chapter.
|
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):
|
block http-client-service
|
||||||
A preliminary version of the example code that will appear in this chapter.
|
: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 — which we've bound conditionally in the
|
||||||
|
template — 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=".")
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
|
block includes
|
||||||
include ../_util-fns
|
include ../_util-fns
|
||||||
:marked
|
:marked
|
||||||
[HTTP](https://tools.ietf.org/html/rfc2616) is the primary protocol for browser/server communication.
|
[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
|
The Angular HTTP client library simplifies application programming of the **XHR** and **JSONP** APIs
|
||||||
as we'll learn in this chapter covering:
|
as we'll learn in this chapter covering:
|
||||||
|
|
||||||
[Http client sample overview](#http-client)<br>
|
ul
|
||||||
[Fetch data with http.get](#fetch-data)<br>
|
li #[a(href="#http-client") Http client sample overview]
|
||||||
[RxJS Observable of HTTP Responses](#rxjs)<br>
|
li #[a(href="#fetch-data") Fetch data with http.get]
|
||||||
[Enabling RxJS Operators](#enable-rxjs-operators)<br>
|
+ifDocsFor('ts')
|
||||||
[Extract JSON data with RxJS map](#map)<br>
|
li #[a(href="#rxjs") RxJS Observable of HTTP Responses]
|
||||||
[Error handling](#error-handling)<br>
|
li #[a(href="#enable-rxjs-operators") Enabling RxJS Operators]
|
||||||
[Send data to the server](#update)<br>
|
li #[a(href="#extract-data") Extract JSON data]
|
||||||
[Add headers](#headers)<br>
|
li #[a(href="#error-handling") Error handling]
|
||||||
[Promises instead of observables](#promises)<br>
|
li #[a(href="#update") Send data to the server]
|
||||||
[JSONP](#jsonp)<br>
|
+ifDocsFor('ts')
|
||||||
[Set query string parameters](#search-parameters)<br>
|
li #[a(href="#promises") Promises instead of observables]
|
||||||
[Debounce search term input](#more-observables)<br>
|
li #[a(href="#cross-origin-requests") Cross-origin requests: Wikipedia example]
|
||||||
[Appendix: the in-memory web api service](#in-mem-web-api)<br>
|
+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
|
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
|
.l-main-section
|
||||||
|
a#http-client
|
||||||
:marked
|
:marked
|
||||||
<a id="http-client"></a>
|
|
||||||
## The *Http* Client Demo
|
## The *Http* Client Demo
|
||||||
|
|
||||||
We use the Angular `Http` client to communicate via `XMLHttpRequest (XHR)`.
|
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.
|
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.
|
It works like this.
|
||||||
figure.image-display
|
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.
|
We're making a point about application structure that is easier to justify when the app grows.
|
||||||
:marked
|
:marked
|
||||||
Here is the `TohComponent` shell:
|
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
|
:marked
|
||||||
As usual, we import the symbols we need. The newcomer is `HTTP_PROVIDERS`,
|
As usual, we import the symbols we need. The newcomer is `HTTP_PROVIDERS`,
|
||||||
an array of service providers from the Angular HTTP library.
|
an array of service providers from the Angular HTTP library.
|
||||||
@ -75,20 +79,20 @@ figure.image-display
|
|||||||
|
|
||||||
:marked
|
:marked
|
||||||
This sample only has one child, the `HeroListComponent`. Here's its template:
|
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
|
: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
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/server-communication/hero-list.png' alt="Hero List")
|
img(src='/resources/images/devguide/server-communication/hero-list.png' alt="Hero List")
|
||||||
:marked
|
:marked
|
||||||
Beneath the heroes is an input box and an *Add Hero* button where we can enter the names of new heroes
|
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.
|
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.
|
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
|
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.
|
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="oninit")
|
||||||
a(id="HeroListComponent")
|
a(id="HeroListComponent")
|
||||||
@ -115,12 +119,14 @@ a(id="HeroListComponent")
|
|||||||
This is a "best practice".
|
This is a "best practice".
|
||||||
Components are easier to test and debug when their constructors are simple and all real work
|
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.
|
(especially calling a remote server) is handled in a separate method.
|
||||||
|
block getheroes-and-addhero
|
||||||
:marked
|
: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`,
|
We subscribe to this `Observable`,
|
||||||
specifying the actions to take when the request succeeds or fails.
|
specifying the actions to take when the request succeeds or fails.
|
||||||
We'll get to observables and subscription shortly.
|
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
|
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.
|
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:
|
returning mock heroes in a service like this one:
|
||||||
+makeExample('toh-4/ts/app/hero.service.ts', 'just-get-heroes')(format=".")
|
+makeExample('toh-4/ts/app/hero.service.ts', 'just-get-heroes')(format=".")
|
||||||
:marked
|
: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`:
|
Here's the new `HeroService`:
|
||||||
|
|
||||||
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'v1', 'app/toh/hero.service.ts')
|
+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
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
`Http` is not part of the Angular core. It's an optional service in its own `@angular/http` library
|
`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
|
that we installed with npm (see the `package.json`) and
|
||||||
registered for module loading by SystemJS (see `systemjs.config.js`)
|
registered for module loading by SystemJS (see `systemjs.config.js`)
|
||||||
|
|
||||||
:marked
|
: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=".")
|
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'http-get', 'app/toh/hero.service.ts (getHeroes)')(format=".")
|
||||||
:marked
|
:marked
|
||||||
We pass the resource URL to `get` and it calls the server which should return heroes.
|
We pass the resource URL to `get` and it calls the server which should return heroes.
|
||||||
@ -152,9 +159,10 @@ a(id="HeroListComponent")
|
|||||||
:marked
|
:marked
|
||||||
It *will* return heroes once we've set up the [in-memory web api](in-mem-web-api)
|
It *will* return heroes once we've set up the [in-memory web api](in-mem-web-api)
|
||||||
described in the appendix below.
|
described in the appendix below.
|
||||||
|
|
||||||
Alternatively, we can (temporarily) target a JSON file by changing the endpoint URL:
|
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=".")
|
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'endpoint-json')(format=".")
|
||||||
|
|
||||||
|
block rxjs
|
||||||
:marked
|
: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
|
||||||
@ -174,7 +182,6 @@ a(id="HeroListComponent")
|
|||||||
|
|
||||||
All of our Developer Guide samples have installed the RxJS npm package and loaded via `system.js`
|
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.
|
because observables are used widely in Angular applications.
|
||||||
|
|
||||||
We certainly need it now when working with the HTTP client.
|
We certainly need it now when working with the HTTP client.
|
||||||
And we must take a critical extra step to make RxJS observables usable.
|
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=".")
|
+makeExample('server-communication/ts/app/main.ts', 'import-rxjs', 'app/main.ts (import rxjs)')(format=".")
|
||||||
|
|
||||||
a(id="map")
|
a#extract-data
|
||||||
a(id="extract-data")
|
|
||||||
:marked
|
:marked
|
||||||
### Process the response object
|
### Process the response object
|
||||||
Remember that our `getHeroes` method mapped the `http.get` response object to heroes with an `extractData` helper method:
|
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 (extractData)')(format=".")
|
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'extract-data', 'app/toh/hero.service.ts (excerpt)')(format=".")
|
||||||
:marked
|
:marked
|
||||||
The `response` object does not hold our data in a form we can use directly.
|
The `response` object does not hold our data in a form we can use directly.
|
||||||
To make it useful in our application we must
|
To make it useful in our application we must
|
||||||
* check for a bad response
|
* check the response status,
|
||||||
* parse the response data into a JSON object
|
* parse the response data into a JSON object
|
||||||
|
|
||||||
|
+ifDocsFor('ts')
|
||||||
.alert.is-important
|
.alert.is-important
|
||||||
:marked
|
:marked
|
||||||
*Beta alert*: error status interception and parsing may be absorbed within `http` when Angular is released.
|
*Beta alert*: error status interception and parsing may be absorbed within `http` when Angular is released.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
#### Bad status codes
|
#### Non-success status codes
|
||||||
A status code outside the 200-300 range is an error from the _application point of view_
|
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_.
|
but it is not an error from the _HTTP point of view_.
|
||||||
For example, a `404 - Not Found` is a response like any other.
|
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.
|
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.
|
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.
|
The `catch` operator that is next in the `getHeroes` observable chain will handle our thrown error.
|
||||||
|
|
||||||
|
:marked
|
||||||
#### Parse to JSON
|
#### Parse to JSON
|
||||||
|
block parse-json
|
||||||
|
:marked
|
||||||
The response data are in JSON string form.
|
The response data are in JSON string form.
|
||||||
We must parse that string into JavaScript objects which we do by calling `response.json()`.
|
We must parse that string into JavaScript objects which we do by calling `response.json()`.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
This is not Angular's own design.
|
This is not Angular's own design.
|
||||||
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
|
.l-sub-section
|
||||||
:marked
|
: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`
|
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
|
||||||
@ -247,50 +263,50 @@ a(id="extract-data")
|
|||||||
Not all servers return an object with a `data` property.
|
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 HTTP response. Bad idea!
|
||||||
|
The point of a data service is to hide the server interaction details from consumers.
|
||||||
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.
|
The component that calls the `HeroService` wants heroes.
|
||||||
It has no interest in what we do to get them.
|
It has no interest in what we do to get them.
|
||||||
It doesn't care where they come from.
|
It doesn't care where they come from.
|
||||||
And it certainly doesn't want to deal with a response object.
|
And it certainly doesn't want to deal with a response object.
|
||||||
|
|
||||||
|
+ifDocsFor('ts')
|
||||||
.callout.is-important
|
.callout.is-important
|
||||||
header HTTP GET is delayed
|
header HTTP GET is delayed
|
||||||
:marked
|
: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)
|
[*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.
|
which means the request won't go out until something *subscribes* to the observable.
|
||||||
That *something* is the [HeroListComponent](#subscribe).
|
That *something* is the [HeroListComponent](#subscribe).
|
||||||
|
|
||||||
a(id="error-handling")
|
a#error-handling
|
||||||
:marked
|
:marked
|
||||||
### Always handle errors
|
### 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.
|
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 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
|
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.
|
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.
|
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.
|
We use the Observable `catch` operator on the service level.
|
||||||
It takes an error handling function with an error object as the argument.
|
It takes an error handling function with an error object as the argument.
|
||||||
Our service handler, `handleError`, logs the response to the console,
|
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`.
|
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#subscribe
|
||||||
<a id="hero-list-component"></a>
|
a#hero-list-component
|
||||||
.l-main-section
|
h4 #[b HeroListComponent] error handling
|
||||||
|
block hlc-error-handling
|
||||||
:marked
|
:marked
|
||||||
## Subscribe in the *HeroListComponent*
|
Back in the `HeroListComponent`, where we called `#{_priv}heroService.getHeroes()`,
|
||||||
Back in the `HeroListComponent`, where we called `heroService.get`,
|
|
||||||
we supply the `subscribe` function with a second function to handle the error message.
|
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.
|
It sets an `errorMessage` variable which we've bound conditionally in the template.
|
||||||
|
|
||||||
@ -307,13 +323,14 @@ a(id="error-handling")
|
|||||||
:marked
|
:marked
|
||||||
## Send data to the server
|
## 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.
|
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
|
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:
|
just the name of a new hero:
|
||||||
code-example(format="." language="javascript").
|
|
||||||
addHero (name: string) : Observable<Hero>
|
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'addhero-sig')(format=".")
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
To implement it, we need to know some details about the server's api for creating heroes.
|
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
|
of the new hero including its generated id. The hero arrives tucked inside a response object
|
||||||
with its own `data` property.
|
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', '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=".")
|
+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
|
:marked
|
||||||
### Headers
|
### 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).
|
[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.
|
Compose the options object and pass it in as the *third* parameter of the `post` method, as shown above.
|
||||||
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'headers', 'app/toh/hero.service.ts (headers)')(format=".")
|
|
||||||
|
: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
|
:marked
|
||||||
### JSON results
|
### 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
|
:marked
|
||||||
Know the shape of the data returned by the server.
|
Back in the `HeroListComponent`, we see that *its* `addHero()` method subscribes to the observable returned by the *service's* `addHero()` method.
|
||||||
*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.
|
|
||||||
When the data arrive it pushes the new hero object into its `heroes` array for presentation to the user.
|
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=".")
|
+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
|
:marked
|
||||||
## Fall back to Promises
|
## 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`.
|
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.
|
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.
|
This is the most common approach for server communication.
|
||||||
It doesn't work in all scenarios.
|
It doesn't work in all scenarios.
|
||||||
|
|
||||||
@ -437,10 +464,14 @@ code-example(format="." language="javascript").
|
|||||||
:marked
|
:marked
|
||||||
### Search wikipedia
|
### 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
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/server-communication/wiki-1.gif' alt="Wikipedia search app (v.1)" width="250")
|
img(src='/resources/images/devguide/server-communication/wiki-1.gif' alt="Wikipedia search app (v.1)" width="250")
|
||||||
|
|
||||||
|
block wikipedia-jsonp+
|
||||||
:marked
|
: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.
|
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.
|
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`.
|
We register that service with `JSONP_PROVIDERS` in the [component below](#wikicomponent) that calls our `WikipediaService`.
|
||||||
|
|
||||||
<a id="query-parameters"></a>
|
<a id="query-parameters"></a>
|
||||||
<a id="search-parameters"></a>
|
|
||||||
:marked
|
:marked
|
||||||
### Search parameters
|
### Search parameters
|
||||||
The [Wikipedia 'opensearch' API](https://www.mediawiki.org/wiki/API:Opensearch)
|
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.
|
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
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## Appendix: Tour of Heroes in-memory server
|
## Appendix: Tour of Heroes in-memory server
|
||||||
@ -597,45 +627,46 @@ figure.image-display
|
|||||||
:marked
|
:marked
|
||||||
We'd set the endpoint to the JSON file like this:
|
We'd set the endpoint to the JSON file like this:
|
||||||
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'endpoint-json')(format=".")
|
+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
|
:marked
|
||||||
The *get heroes* scenario would work.
|
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.
|
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.
|
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.
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
: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
|
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`)
|
registered for module loading by SystemJS (see `systemjs.config.js`)
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The *in-memory web api* gets its data from a custom, application class with a `createDb()` method that returns
|
The in-memory web API gets its data from #{_a_ca_class_with} a `createDb()`
|
||||||
a "database" object whose keys are collection names ("heroes")
|
method that returns a map whose keys are collection names and whose values
|
||||||
and whose values are arrays of objects in those collections.
|
are #{_array}s 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
|
||||||
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=".")
|
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'endpoint')(format=".")
|
||||||
:marked
|
:marked
|
||||||
Finally, we tell Angular itself to direct its http requests to the *in-memory web api* rather
|
Finally, we need to redirect client HTTP requests to the in-memory web API.
|
||||||
than externally to a remote server.
|
block redirect-to-web-api
|
||||||
|
:marked
|
||||||
This redirection is easy because Angular's `http` service delegates the client/server communication tasks
|
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 a helper service called the `XHRBackend`.
|
||||||
|
|
||||||
To enable our server simulation, we replace the default `XHRBackend` service with
|
To enable our server simulation, we replace the default `XHRBackend` service with
|
||||||
the *in-memory web api service* using standard Angular provider registration
|
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.
|
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:
|
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=".")
|
+makeExample('server-communication/ts/app/toh/toh.component.ts', 'in-mem-web-api-imports', 'toh.component.ts (web API imports)')(format=".")
|
||||||
:marked
|
:marked
|
||||||
Then we add the following two provider definitions to the `providers` array in component 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
|
|
||||||
See the full source code in the [live example](/resources/live-examples/server-communication/ts/plnkr.html).
|
|
||||||
|
|
||||||
|
p See the full source code in the #[+liveExampleLink2('live example', 'server-communication')].
|
||||||
|
Loading…
x
Reference in New Issue
Block a user