docs(server-communication): heavily refactored (TS & Dart)

This commit is contained in:
Ward Bell 2016-05-17 21:25:41 -07:00
parent a4bc455030
commit 29511831cd
27 changed files with 391 additions and 307 deletions

View File

@ -0,0 +1,31 @@
// #docplaster
// #docregion
import "package:angular2/core.dart" show Component;
import "toh/hero_list_component.dart" show HeroListComponent;
import "wiki/wiki_component.dart" show WikiComponent;
import "wiki/wiki_smart_component.dart" show WikiSmartComponent;
@Component(
selector: "my-app",
template: '''
<hero-list></hero-list>
<my-wiki></my-wiki>
<my-wiki-smart></my-wiki-smart>
''',
// #enddocregion
/*
// #docregion http-providers
providers: const [
// in-memory web api provider
const Provider(BrowserClient,
useFactory: HttpClientBackendServiceFactory, deps: const [])],
// #enddocregion http-providers
*/
// #docregion
directives: const [
HeroListComponent,
WikiComponent,
WikiSmartComponent
])
class AppComponent {}

View File

@ -2,7 +2,7 @@
import 'package:http/browser_client.dart'; 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 createDb = () => { CreateDb _createDb = () => {
'heroes': [ 'heroes': [
{"id": "1", "name": "Windstorm"}, {"id": "1", "name": "Windstorm"},
{"id": "2", "name": "Bombasto"}, {"id": "2", "name": "Bombasto"},
@ -12,4 +12,4 @@ CreateDb createDb = () => {
}; };
BrowserClient HttpClientBackendServiceFactory() => BrowserClient HttpClientBackendServiceFactory() =>
new HttpClientInMemoryBackendService(createDb); new HttpClientInMemoryBackendService(_createDb);

View File

@ -9,7 +9,7 @@ import 'hero_service.dart';
@Component( @Component(
selector: 'hero-list', selector: 'hero-list',
templateUrl: 'hero_list_component.html', templateUrl: 'hero_list_component.html',
styles: const ['.error {color:red;}']) providers: const [HeroService])
// #docregion component // #docregion component
class HeroListComponent implements OnInit { class HeroListComponent implements OnInit {
final HeroService _heroService; final HeroService _heroService;

View File

@ -1,4 +1,5 @@
<!-- #docregion --> <!-- #docregion -->
<h1>Tour of Heroes</h1>
<h3>Heroes:</h3> <h3>Heroes:</h3>
<ul> <ul>
<li *ngFor="let hero of heroes"> <li *ngFor="let hero of heroes">

View File

@ -15,7 +15,9 @@ class HeroService {
// #enddocregion endpoint, http-get // #enddocregion endpoint, http-get
final BrowserClient _http; final BrowserClient _http;
// #docregion ctor
HeroService(this._http); HeroService(this._http);
// #enddocregion ctor
// #docregion methods, error-handling, http-get // #docregion methods, error-handling, http-get
Future<List<Hero>> getHeroes() async { Future<List<Hero>> getHeroes() async {
@ -57,6 +59,7 @@ class HeroService {
Exception _handleError(dynamic e) { Exception _handleError(dynamic e) {
// In a real world app, we might use a remote logging infrastructure // In a real world app, we might use a remote logging infrastructure
// We'd also dig deeper into the error to get a better message
print(e); // log to console instead print(e); // log to console instead
return new Exception('Server error; cause: $e'); return new Exception('Server error; cause: $e');
} }

View File

@ -1,36 +0,0 @@
// #docplaster
// #docregion
import 'package:angular2/core.dart';
import 'package:http/browser_client.dart';
import 'hero_list_component.dart';
import 'hero_service.dart';
// #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: '''
<h1>Tour of Heroes</h1>
<hero-list></hero-list>
''',
// #enddocregion template
directives: const [HeroListComponent],
// #enddocregion
// #docregion in-mem-web-api
/* ... */
// #docregion
providers: const [
HeroService,
// #enddocregion
// in-memory web api providers
const Provider(BrowserClient,
useFactory: HttpClientBackendServiceFactory)
// #docregion
])
class TohComponent {}

View File

@ -12,11 +12,15 @@ dependencies:
jsonpadding: ^0.1.0 jsonpadding: ^0.1.0
stream_transformers: ^0.3.0+3 stream_transformers: ^0.3.0+3
http_in_memory_web_api: ^0.0.1 http_in_memory_web_api: ^0.0.1
# #docregion transformers
transformers: transformers:
- angular2: - angular2:
platform_directives: 'package:angular2/common.dart#CORE_DIRECTIVES' platform_directives:
platform_pipes: 'package:angular2/common.dart#COMMON_PIPES' - 'package:angular2/common.dart#CORE_DIRECTIVES'
platform_pipes:
- 'package:angular2/common.dart#COMMON_PIPES'
entry_points: 'web/main.dart' entry_points: 'web/main.dart'
resolved_identifiers: resolved_identifiers:
BrowserClient: 'package:http/browser_client.dart' BrowserClient: 'package:http/browser_client.dart'
- dart_to_js_script_rewriter - dart_to_js_script_rewriter
# #enddocregion transformers

View File

@ -6,15 +6,14 @@
<meta charset="UTF-8"> <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"> <link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="sample.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>
<body> <body>
<my-toh>ToH Loading...</my-toh> <my-app>Loading...</my-app>
<my-wiki>Wiki Loading...</my-wiki>
<my-wiki-smart>WikiSmart Loading...</my-wiki-smart>
</body> </body>
</html> </html>

View File

@ -1,12 +1,34 @@
// #docregion // #docplaster
import 'package:angular2/platform/browser.dart'; // #docregion final
import 'package:angular2/core.dart' show Provider;
// #docregion v1
import 'package:angular2/platform/browser.dart' show bootstrap;
// #docregion http-providers
import 'package:http/browser_client.dart' show BrowserClient;
// #enddocregion http-providers
import 'package:server_communication/toh/toh_component.dart'; import 'package:server_communication/app_component.dart';
import 'package:server_communication/wiki/wiki_component.dart'; // #enddocregion v1
import 'package:server_communication/wiki/wiki_smart_component.dart'; // #docregion in-mem-web-api-imports
import "package:server_communication/hero_data.dart";
main() { // #enddocregion in-mem-web-api-imports
bootstrap(TohComponent); // #docregion in-mem-web-api-providers
bootstrap(WikiComponent); void main() {
bootstrap(WikiSmartComponent); bootstrap(AppComponent, const [
// in-memory web api provider
const Provider(BrowserClient,
useFactory: HttpClientBackendServiceFactory, deps: const [])
// TODO: drop `deps` once fix lands for
// https://github.com/angular/angular/issues/5266
]);
} }
// #enddocregion final, in-mem-web-api-providers
/*
// #docregion v1
void main() {
bootstrap(AppComponent, const [BrowserClient]);
}
// #enddocregion v1
*/

View File

@ -0,0 +1 @@
.error {color:red;}

View File

@ -4,52 +4,45 @@ describe('Server Communication', function () {
browser.get(''); browser.get('');
}); });
describe('Tour of Heroes e2e tests', function () { describe('Tour of Heroes (Observable)', function () {
var _initialHeroCount = 4; var initialHeroCount = 4;
var _newHeroName = 'Mr. IQ'; var newHeroName = 'Mr. IQ';
var _heroCountAfterAdd = 5; var heroCountAfterAdd = 5;
it('should display ' + _initialHeroCount + ' heroes after init', function () { var heroListComp = element(by.tagName('hero-list'));
var myTohComp = element(by.tagName('my-toh')); var addButton = heroListComp.element(by.tagName('button'));
expect(myTohComp).toBeDefined('<my-toh> must exist'); var heroTags = heroListComp.all(by.tagName('li'));
var heroListComp = myTohComp.element(by.tagName('hero-list')); var heroNameInput = heroListComp.element(by.tagName('input'));
it('should exist', function() {
expect(heroListComp).toBeDefined('<hero-list> must exist'); expect(heroListComp).toBeDefined('<hero-list> must exist');
var heroTags = heroListComp.all(by.tagName('li')); });
expect(heroTags.count()).toBe(_initialHeroCount);
it('should display ' + initialHeroCount + ' heroes after init', function () {
expect(heroTags.count()).toBe(initialHeroCount);
}); });
it('should not add hero with empty name', function () { it('should not add hero with empty name', function () {
var myTohComp = element(by.tagName('my-toh'));
expect(myTohComp).toBeDefined('<my-toh> must exist');
var addButton = myTohComp.element(by.tagName('button'));
expect(addButton).toBeDefined('"Add Hero" button must be defined'); expect(addButton).toBeDefined('"Add Hero" button must be defined');
addButton.click().then(function() { addButton.click().then(function() {
var heroListComp = myTohComp.element(by.tagName('hero-list')); expect(heroTags.count()).toBe(initialHeroCount, 'No new hero should be added');
var heroTags = heroListComp.all(by.tagName('li'));
expect(heroTags.count()).toBe(_initialHeroCount, 'No new hero should be added');
}); });
}) })
it('should add a new hero to the list', function () { it('should add a new hero to the list', function () {
var myTohComp = element(by.tagName('my-toh'));
expect(myTohComp).toBeDefined('<my-toh> must exist');
var heroNameInput = myTohComp.element(by.tagName('input'));
expect(heroNameInput).toBeDefined('<input> for hero name must exist'); expect(heroNameInput).toBeDefined('<input> for hero name must exist');
var addButton = myTohComp.element(by.tagName('button'));
expect(addButton).toBeDefined('"Add Hero" button must be defined'); expect(addButton).toBeDefined('"Add Hero" button must be defined');
sendKeys(heroNameInput, _newHeroName); sendKeys(heroNameInput, newHeroName);
addButton.click().then(function() { addButton.click().then(function() {
var heroListComp = myTohComp.element(by.tagName('hero-list')); expect(heroTags.count()).toBe(heroCountAfterAdd, 'A new hero should be added');
var heroTags = heroListComp.all(by.tagName('li')); var newHeroInList = heroTags.get(heroCountAfterAdd - 1).getText();
expect(heroTags.count()).toBe(_heroCountAfterAdd, 'A new hero should be added'); expect(newHeroInList).toBe(newHeroName, 'The hero should be added to the end of the list');
var newHeroInList = heroTags.get(_heroCountAfterAdd - 1).getText();
expect(newHeroInList).toBe(_newHeroName, 'The hero should be added to the end of the list');
}); });
}) })
}); });
describe('Wikipedia Demo e2e tests', function () { describe('Wikipedia Demo', function () {
it('should initialize the demo with empty result list', function () { it('should initialize the demo with empty result list', function () {
var myWikiComp = element(by.tagName('my-wiki')); var myWikiComp = element(by.tagName('my-wiki'));
@ -81,7 +74,7 @@ describe('Server Communication', function () {
} }
}); });
describe('Smarter Wikipedia Demo e2e tests', function () { describe('Smarter Wikipedia Demo', function () {
it('should initialize the demo with empty result list', function () { it('should initialize the demo with empty result list', function () {
var myWikiSmartComp = element(by.tagName('my-wiki-smart')); var myWikiSmartComp = element(by.tagName('my-wiki-smart'));

View File

@ -1,9 +0,0 @@
// #docregion
// import 'rxjs/Rx'; // adds ALL RxJS operators to Observable
// Just the Observable operators we need for THIS app.
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/switchMap';

View File

@ -0,0 +1,37 @@
// #docplaster
// #docregion
import { Component } from '@angular/core';
// #docregion import-rxjs
// Add the RxJS Observable operators we need in this app.
import './rxjs-operators';
// #enddocregion import-rxjs
import { HeroListComponent } from './toh/hero-list.component';
import { HeroListPromiseComponent } from './toh/hero-list.component.promise';
import { WikiComponent } from './wiki/wiki.component';
import { WikiSmartComponent } from './wiki/wiki-smart.component';
@Component({
selector: 'my-app',
template: `
<hero-list></hero-list>
<hero-list-promise></hero-list-promise>
<my-wiki></my-wiki>
<my-wiki-smart></my-wiki-smart>
`,
// #enddocregion
/*
// #docregion http-providers
providers: [ HTTP_PROVIDERS ]
// #enddocregion http-providers
*/
// #docregion
directives: [
HeroListComponent, HeroListPromiseComponent,
WikiComponent, WikiSmartComponent
]
})
export class AppComponent { }
// #enddocregion

View File

@ -1,21 +1,30 @@
// #docplaster // #docplaster
// #docregion // #docregion final
// Imports for loading & configuring the in-memory web api
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';
// The usual bootstrapping imports
// #docregion v1
import { bootstrap } from '@angular/platform-browser-dynamic'; import { bootstrap } from '@angular/platform-browser-dynamic';
// #docregion http-providers
import { HTTP_PROVIDERS } from '@angular/http'; import { HTTP_PROVIDERS } from '@angular/http';
// #enddocregion http-providers
// #docregion import-rxjs import { AppComponent } from './app.component';
// Add the RxJS Observable operators we need in this app.
import './add-rxjs-operators';
// #enddocregion import-rxjs
import { TohComponent } from './toh/toh.component'; // #enddocregion v1, final
import { WikiComponent } from './wiki/wiki.component'; /*
import { WikiSmartComponent } from './wiki/wiki-smart.component'; // #docregion v1
bootstrap(AppComponent, [ HTTP_PROVIDERS ]);
// #docregion http-providers // #enddocregion v1
bootstrap(TohComponent, [HTTP_PROVIDERS]); */
// #enddocregion http-providers // #docregion final
bootstrap(WikiComponent); bootstrap(AppComponent, [
bootstrap(WikiSmartComponent); HTTP_PROVIDERS,
provide(XHRBackend, { useClass: InMemoryBackendService }), // in-mem server
provide(SEED_DATA, { useClass: HeroData }) // in-mem server data
]);
// #enddocregion final

View File

@ -0,0 +1,16 @@
// #docregion
// import 'rxjs/Rx'; // adds ALL RxJS statics & operators to Observable
// See node_module/rxjs/Rxjs.js
// Import just the rxjs statics and operators we need for THIS app.
// Statics
import 'rxjs/add/observable/throw';
// Operators
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/toPromise';

View File

@ -1,4 +1,5 @@
<!-- #docregion --> <!-- #docregion -->
<h1>Tour of Heroes ({{mode}})</h1>
<h3>Heroes:</h3> <h3>Heroes:</h3>
<ul> <ul>
<li *ngFor="let hero of heroes"> <li *ngFor="let hero of heroes">

View File

@ -1,21 +1,22 @@
// ToH Promise Version
// #docregion // #docregion
// Promise Version
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Hero } from './hero'; import { Hero } from './hero';
import { HeroService } from './hero.service.1'; import { HeroService } from './hero.service.promise';
@Component({ @Component({
selector: 'hero-list', selector: 'hero-list-promise',
templateUrl: 'app/toh/hero-list.component.html', templateUrl: 'app/toh/hero-list.component.html',
styles: ['.error {color:red;}'] providers: [ HeroService ]
}) })
// #docregion component // #docregion component
export class HeroListComponent implements OnInit { export class HeroListPromiseComponent implements OnInit {
constructor (private heroService: HeroService) {} constructor (private heroService: HeroService) {}
errorMessage: string; errorMessage: string;
heroes: Hero[]; heroes: Hero[];
mode = 'Promise';
ngOnInit() { this.getHeroes(); } ngOnInit() { this.getHeroes(); }

View File

@ -1,13 +1,13 @@
// #docregion // #docregion
// Observable Version
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Hero } from './hero'; import { Hero } from './hero';
import { HeroService } from './hero.service'; import { HeroService } from './hero.service';
@Component({ @Component({
selector: 'hero-list', selector: 'hero-list',
templateUrl: 'app/toh/hero-list.component.html', templateUrl: 'app/toh/hero-list.component.html',
styles: ['.error {color:red;}'] providers: [ HeroService ]
}) })
// #docregion component // #docregion component
export class HeroListComponent implements OnInit { export class HeroListComponent implements OnInit {
@ -15,7 +15,8 @@ export class HeroListComponent implements OnInit {
constructor (private heroService: HeroService) {} constructor (private heroService: HeroService) {}
errorMessage: string; errorMessage: string;
heroes:Hero[]; heroes: Hero[];
mode = 'Observable';
ngOnInit() { this.getHeroes(); } ngOnInit() { this.getHeroes(); }

View File

@ -1,7 +1,6 @@
// ToH Promise Version
// #docplaster // #docplaster
// #docregion // #docregion
// Promise Version
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http'; import { Http, Response } from '@angular/http';
import { Headers, RequestOptions } from '@angular/http'; import { Headers, RequestOptions } from '@angular/http';
@ -41,7 +40,8 @@ export class HeroService {
private handleError (error: any) { private handleError (error: any) {
// In a real world app, we might use a remote logging infrastructure // In a real world app, we might use a remote logging infrastructure
// We'd also dig deeper into the error to get a better message // We'd also dig deeper into the error to get a better message
let errMsg = error.message || error.statusText || 'Server error'; let errMsg = (error.message) ? error.message :
error.status ? `${error.status} - ${error.statusText}` : 'Server error';
console.error(errMsg); // log to console instead console.error(errMsg); // log to console instead
return Promise.reject(errMsg); return Promise.reject(errMsg);
} }

View File

@ -1,5 +1,6 @@
// #docplaster // #docplaster
// #docregion // #docregion
// Observable Version
// #docregion v1 // #docregion v1
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http'; import { Http, Response } from '@angular/http';
@ -14,7 +15,9 @@ import { Observable } from 'rxjs/Observable';
@Injectable() @Injectable()
export class HeroService { export class HeroService {
// #docregion ctor
constructor (private http: Http) {} constructor (private http: Http) {}
// #enddocregion ctor
// #docregion endpoint // #docregion endpoint
private heroesUrl = 'app/heroes'; // URL to web API private heroesUrl = 'app/heroes'; // URL to web API
@ -52,7 +55,8 @@ export class HeroService {
private handleError (error: any) { private handleError (error: any) {
// In a real world app, we might use a remote logging infrastructure // In a real world app, we might use a remote logging infrastructure
// We'd also dig deeper into the error to get a better message // We'd also dig deeper into the error to get a better message
let errMsg = error.message || error.statusText || 'Server error'; let errMsg = (error.message) ? error.message :
error.status ? `${error.status} - ${error.statusText}` : 'Server error';
console.error(errMsg); // log to console instead console.error(errMsg); // log to console instead
return Observable.throw(errMsg); return Observable.throw(errMsg);
} }

View File

@ -1,6 +1,6 @@
// #docregion // #docregion
export class Hero { export class Hero {
constructor( constructor(
public id:number, public id: number,
public name:string) { } public name: string) { }
} }

View File

@ -1,43 +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

@ -6,6 +6,8 @@
<meta charset="UTF-8"> <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"> <link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="sample.css">
<!-- Polyfill(s) for older browsers --> <!-- Polyfill(s) for older browsers -->
<script src="node_modules/es6-shim/es6-shim.min.js"></script> <script src="node_modules/es6-shim/es6-shim.min.js"></script>
@ -21,9 +23,7 @@
</head> </head>
<body> <body>
<my-toh>ToH Loading...</my-toh> <my-app>Loading...</my-app>
<my-wiki>Wiki Loading...</my-wiki>
<my-wiki-smart>WikiSmart Loading...</my-wiki-smart>
</body> </body>
</html> </html>

View File

@ -0,0 +1 @@
.error {color:red;}

View File

@ -2,10 +2,40 @@ extends ../../../ts/latest/guide/server-communication.jade
block includes block includes
include ../_util-fns include ../_util-fns
- var _Http = 'BrowserClient';
- var _Angular_Http = 'Dart <code>BrowserClient</code>'
- var _httpUrl = 'https://pub.dartlang.org/packages/http'
- var _Angular_http_library = 'Dart <a href="!{_httpUrl}"><b>http</b> library</a>'
block demos-list
li HTTP client: Tour of Heroes
li JSONP client: Wikipedia to fetch data from a service that doesn't support CORS (under development)
block rxjs-import
//- N/A
block http-client
block system-config-of-http
//- N/A
block http-providers block http-providers
//- TODO(chalin): mention the Angular transformer resolved_identifiers. :marked
//- Maybe not yet at this point in the chapter. Actually, it is unnecessary to include `BrowserClient` in the list of providers.
***But*** as is mentioned in the *Angular 2 Dart Transformer* [wiki page][ng2dtri],
the template compiler _generates_ dependency injection code, hence all the
identifiers used in DI have to be collected by the Angular 2 transformer
so that the libraries containing these identifiers can be transformed.
Unless special steps are taken, Dart libraries like `http`
are not transformed. To ensure that the `BrowserClient` identifier is available
for DI, we must add a `resolved_identifiers` parameter to the `angular2`
transformer in `pubspec.yaml`:
[ng2dtri]: https://github.com/angular/angular/wiki/Angular-2-Dart-Transformer#resolved_identifiers
- var stylePattern = { pnk: /(resolved_identifiers:|Browser.*)/gm, otl: /(- angular2:)|(transformers:)/g };
+makeExcerpt('pubspec.yaml', 'transformers', 'pubspec.yaml (transformers)', stylePattern)
block getheroes-and-addhero block getheroes-and-addhero
:marked :marked
@ -20,27 +50,13 @@ block getheroes-and-addhero
programming in Dart, or the tutorial on programming in Dart, or the tutorial on
[_Asynchronous Programming: Futures_](https://www.dartlang.org/docs/tutorials/futures/). [_Asynchronous Programming: Futures_](https://www.dartlang.org/docs/tutorials/futures/).
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 block rxjs
//- N/A //- 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 block parse-json
:marked :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 We must parse that string into Objects which we do by calling
the `JSON.decode()` method from the `dart:convert` library. the `JSON.decode()` method from the `dart:convert` library.
block error-handling block error-handling
@ -58,14 +74,13 @@ block hero-list-comp-add-hero
Back in the `HeroListComponent`, we see that *its* `addHero()` Back in the `HeroListComponent`, we see that *its* `addHero()`
awaits for the *service's* asynchronous `addHero()` to return, and when it does, 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. 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 block promises
//- N/A //- N/A
block wikipedia-jsonp+ block wikipedia-jsonp+
:marked :marked
Wikipedia offers both `CORS` and `JSONP` search APIs. Wikipedia offers a modern `CORS` API and a legacy `JSONP` search API.
.alert.is-important .alert.is-important
:marked :marked
The remaining content of this section is coming soon. The remaining content of this section is coming soon.
@ -77,6 +92,4 @@ block redirect-to-web-api
:marked :marked
To achieve this, we have Angular inject an in-memory web API server To achieve this, we have Angular inject an in-memory web API server
instance as a provider for the `BrowserClient`. This is possible because instance as a provider for the `BrowserClient`. This is possible because
the in-memory web API server class extends `BrowserClient`. Here are the the in-memory web API server class extends `BrowserClient`.
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,5 +1,9 @@
block includes block includes
include ../_util-fns include ../_util-fns
- var _Http = 'Http'; // Angular `Http` library name.
- var _Angular_Http = 'Angular <code>Http</code>'
- var _Angular_http_library = 'Angular HTTP library'
: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.
.l-sub-section .l-sub-section
@ -16,7 +20,7 @@ block includes
as we'll learn in this chapter covering: as we'll learn in this chapter covering:
ul ul
li #[a(href="#http-client") Http client sample overview] li #[a(href="#http-client") HTTP client sample overview]
li #[a(href="#fetch-data") Fetch data with http.get] li #[a(href="#fetch-data") Fetch data with http.get]
+ifDocsFor('ts') +ifDocsFor('ts')
li #[a(href="#rxjs") RxJS Observable of HTTP Responses] li #[a(href="#rxjs") RxJS Observable of HTTP Responses]
@ -36,78 +40,109 @@ p.
We illustrate these topics with code that you can We illustrate these topics with code that you can
#[+liveExampleLink2('run live in a browser', 'server-communication')]. #[+liveExampleLink2('run live in a browser', 'server-communication')].
.l-main-section
h1 Demos
p This chapter describes server communication with the help of the following demos
ul
block demos-list
li HTTP client: Tour of Heroes with Observables
li HTTP client: Tour of Heroes with #{_Promise}s
li JSONP client: Wikipedia to fetch data from a service that doesn't support CORS
li JSONP client: Wikipedia using observable operators to reduce server calls
:marked
These demos are orchestrated by the root `AppComponent`
+makeExample('server-communication/ts/app/app.component.ts', null, 'app/app.component.ts')
block rxjs-import
:marked
There is nothing remarkable here _except_ for the import of RxJS operators.
+makeExample('server-communication/ts/app/app.component.ts', 'import-rxjs')(format='.')
:marked
We'll talk about that [below](#rxjs) when we're ready to explore observables.
:marked
First, we have to configure our application to use server communication facilities.
.l-main-section
h1#http-providers Providing HTTP Services
:marked
We use the !{_Angular_Http} client to communicate with a server using a familiar HTTP request/response protocol.
The `!{_Http}` client is one of a family of services in the !{_Angular_http_library}.
block system-config-of-http
.l-sub-section
:marked
SystemJS knows how to load services from the !{_Angular_http_library} when we import from the `@angular/http` module
because we registered that module name in the `system.config` file.
:marked
Before we can use the `!{_Http}` client , we'll have to register it as a service provider with the Dependency Injection system.
.l-sub-section
:marked
Learn about providers in the [Dependency Injection](dependency-injection.html) chapter.
p In this demo, we register providers in the #[code bootstrap] method of #[code #[+adjExPath('app/main.ts')]].
+makeExample('server-communication/ts/app/main.ts', 'v1', 'app/main.ts (v1)')(format='.')
block http-providers
:marked
We begin by importing the symbols we need, most of them familiar by now. The newcomer is `HTTP_PROVIDERS`,
a collection of service providers from the Angular HTTP library.
We register HTTP providers in the bootstrap method by passing them in an array as the second parameter after the root component.
### Why register in *bootstrap*?
We prefer to register application-wide providers in the metadata `providers` array
of the root `AppComponent` like this:
+makeExample('server-communication/ts/app/app.component.ts','http-providers')(format='.')
:marked
Here we register the providers in the `bootstrap` method in the `main.ts` file. Why?
This is a *sample application* that doesn't talk to a real server.
We're going to reconfigure the (typically-hidden) `XhrBackend` service with a fake provider
that fetches and saves sample data from an in-memory data store.
This replacement service is called the [*in-memory web api*](#in-mem-web-api).
Such sleight-of-hand is something the root application component should *not* know about.
For this reason, and this reason *only*, we hide it *above* the `AppComponent` in `main.ts`.
.l-main-section .l-main-section
a#http-client a#http-client
:marked :marked
## The *Http* Client Demo # The Tour of Heroes *HTTP* Client Demo
We use the Angular `Http` client to communicate via `XMLHttpRequest (XHR)`. Our first demo is 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 saves 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.
We use the !{_Angular_Http} client to communicate via `XMLHttpRequest (XHR)`.
It works like this. It works like this.
figure.image-display figure.image-display
img(src='/resources/images/devguide/server-communication/http-toh.gif' alt="ToH mini app" width="250") img(src='/resources/images/devguide/server-communication/http-toh.gif' alt="ToH mini app" width="250")
:marked :marked
It's implemented with two components &mdash; a parent `TohComponent` shell and the `HeroListComponent` child. This demo has a single component, the `HeroListComponent`. Here's its template:
We've seen these kinds of component in many other documentation samples.
Let's see how they change to support communication with a server.
.l-sub-section
:marked
We're overdoing the "separation of concerns" by creating two components for a tiny demo.
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/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.
We'll be using that library to access the server.
We also import a `HeroService` that we'll look at shortly.
The component specifies both the ``HTTP_PROVIDERS` and the `HeroService` in the metadata `providers` array,
making them available to the child components of this "Tour of Heroes" application.
.l-sub-section
:marked
Alternatively, we may choose to add the `HTTP_PROVIDERS` while bootstrapping the app:
+makeExample('server-communication/ts/app/main.ts','http-providers','app/main.ts')(format='.')
:marked
Learn about providers in the [Dependency Injection](dependency-injection.html) chapter.
:marked
This sample only has one child, the `HeroListComponent`. 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. It presents the list of heroes with an `ngFor`.
figure.image-display Below the list is an input box and an *Add Hero* button where we can enter the names of new heroes
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. and add them to the database.
We use a [template reference variable](template-syntax.html#ref-vars), `newHeroName`, 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 it ready for a new hero name.
Below the button is an area for an error message. Below the button is an area for an error message.
a(id="oninit") a#oninit
a(id="HeroListComponent") a#HeroListComponent
:marked :marked
### The *HeroListComponent* class ## The *HeroListComponent* class
Here's the component class: Here's the component class:
+makeExample('server-communication/ts/app/toh/hero-list.component.ts','component', 'app/toh/hero-list.component.ts (class)') +makeExample('server-communication/ts/app/toh/hero-list.component.ts','component', 'app/toh/hero-list.component.ts (class)')
:marked :marked
We [inject](dependency-injection.html) the `HeroService` into the constructor. Angular [injects](dependency-injection.html) a `HeroService` into the constructor
That's the instance of the `HeroService` that we provided in the parent shell `TohComponent`. and the component calls that service to fetch and save data.
Notice that the component **does not talk to the server directly!** The component **does not talk directly to the !{_Angular_Http} client**!
The component doesn't know or care how we get the data. The component doesn't know or care how we get the data.
Those details it delegates to the `heroService` class (which we'll get to in a moment). It delegates to the `HeroService`.
This is a golden rule: **always delegate data access to a supporting service class**. This is a golden rule: **always delegate data access to a supporting service class**.
@ -117,40 +152,39 @@ a(id="HeroListComponent")
and count on Angular to call `ngOnInit` when it instantiates this component. and count on Angular to call `ngOnInit` when it instantiates this component.
.l-sub-section .l-sub-section
:marked :marked
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 block getheroes-and-addhero
:marked :marked
The service's `getHeroes()` and `addHero()` methods return an `Observable` of HTTP hero data. The service's `getHeroes()` and `addHero()` methods return an `Observable` of hero data that the !{_Angular_Http} client fetched from the server.
We subscribe to this `Observable`,
specifying the actions to take when the request succeeds or fails. *Observables* are a big topic, beyond the scope of this chapter.
We'll get to observables and subscription shortly. But we need to know a little about them to appreciate what is going on here.
We should think of an `Observable` as a stream of events published by some source.
We listen for events in this stream by ***subscribing*** to the `Observable`.
In these subscriptions we specify the actions to take when the web request
produces a success event (with the hero data in the event payload) or a fail event (with the error in the payload).
:marked :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're ready to look inside the `HeroService`.
and the client-side `HeroService` that talks to it.
### Fetch data
.l-main-section
a#HeroService
h2#fetch-data Fetch data with the #[b HeroService]
:marked
In many of our previous samples we faked the interaction with the server by In many of our previous samples we faked the interaction with the server by
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 a (browser-based) HTTP client service. In this chapter, we revise that `HeroService` to get the heroes from the server using the !{_Angular_Http} client service:
Here's the new `HeroService`: +makeExample('server-communication/ts/app/toh/hero.service.ts', 'v1', 'app/toh/hero.service.ts (revised)')
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'v1', 'app/toh/hero.service.ts')
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
Notice that the Angular `!{_Http}` client service is
[injected](dependency-injection.html) into the `HeroService` constructor.
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'ctor')
:marked :marked
Look closely at how we call `#{_priv}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=".")
@ -166,7 +200,8 @@ block http-client-service
block rxjs 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 who are familiar with asynchronous methods in modern JavaScript would expect the `get` method to return a
[promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). [promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
We'd expect to chain a call to `then()` and extract the heroes. We'd expect to chain a call to `then()` and extract the heroes.
Instead we're calling a `map()` method. Instead we're calling a `map()` method.
@ -177,7 +212,7 @@ block rxjs
.l-main-section .l-main-section
:marked :marked
### RxJS Library # RxJS Library
[RxJS](https://github.com/ReactiveX/RxJS) ("Reactive Extensions") is a 3rd party library, endorsed by Angular, [RxJS](https://github.com/ReactiveX/RxJS) ("Reactive Extensions") is a 3rd party library, endorsed by Angular,
that implements the [*asynchronous observable*](https://www.youtube.com/watch?v=UHI0AzD_WfY "Rob Wormald on observables") pattern. that implements the [*asynchronous observable*](https://www.youtube.com/watch?v=UHI0AzD_WfY "Rob Wormald on observables") pattern.
@ -201,22 +236,25 @@ block rxjs
While that is the easiest thing to do, we'd pay a penalty in extended launch time and application size While that is the easiest thing to do, we'd pay a penalty in extended launch time and application size
because the full library is so big. We only use a few operators in our app. because the full library is so big. We only use a few operators in our app.
Instead, we'll import each operator, one-by-one, until we have a custom *Observable* implementation tuned Instead, we'll import each `Observable` operator and static class method, one-by-one, until we have a custom *Observable* implementation tuned
precisely to our requirements. We'll put the `import` statements in one `app/add-rxjs-operators.ts` file. precisely to our requirements. We'll put the `import` statements in one `app/add-rxjs-operators.ts` file.
+makeExample('server-communication/ts/app/add-rxjs-operators.ts', null, 'app/add-rxjs-operators.ts')(format=".") +makeExample('server-communication/ts/app/rxjs-operators.ts', null, 'app/rxjs-operators.ts')(format=".")
:marked :marked
If we forget an operator, the compiler will warn that it's missing and we'll update this file. If we forget an operator, the TypeScript compiler will warn that it's missing and we'll update this file.
.l-sub-section .l-sub-section
:marked :marked
We don't need _all_ of these particular operators in the `HeroService` &mdash; just `map` and `catch`. We don't need _all_ of these particular operators in the `HeroService` &mdash; just `map`, `catch` and `throw`.
We'll need the others later, in a *Wiki* example [below](#more-observables). We'll need the other operators later, in a *Wiki* example [below](#more-observables).
:marked :marked
Finally, we import `add-rxjs-operator`_itself_ in our `main.ts`: Finally, we import `rxjs-operator`_itself_ in our `app.component.ts`:
+makeExample('server-communication/ts/app/main.ts', 'import-rxjs', 'app/main.ts (import rxjs)')(format=".") +makeExample('server-communication/ts/app/app.component.ts', 'import-rxjs', 'app/app.component.ts (import rxjs)')(format=".")
:marked
Let's return to our study of the `HeroService`.
l-main-section
a#extract-data a#extract-data
:marked :marked
### Process the response object ## Process the response object
Remember that our `getHeroes()` method mapped the `#{_priv}http.get` response object to heroes with an `#{_priv}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 (excerpt)')(format=".") +makeExample('server-communication/ts/app/toh/hero.service.ts', 'extract-data', 'app/toh/hero.service.ts (excerpt)')(format=".")
:marked :marked
@ -293,8 +331,8 @@ h4 #[b HeroListComponent] error handling
block hlc-error-handling block hlc-error-handling
:marked :marked
Back in the `HeroListComponent`, where we called `#{_priv}heroService.getHeroes()`, Back in the `HeroListComponent`, where we called `#{_priv}heroService.getHeroes()`,
we supply the `subscribe` function with a second function to handle the error message. we supply the `subscribe` function with a second function parameter 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 `HeroListComponent` template.
+makeExample('server-communication/ts/app/toh/hero-list.component.ts', 'getHeroes', 'app/toh/hero-list.component.ts (getHeroes)')(format=".") +makeExample('server-communication/ts/app/toh/hero-list.component.ts', 'getHeroes', 'app/toh/hero-list.component.ts (getHeroes)')(format=".")
@ -370,8 +408,8 @@ code-example(format="." language="javascript").
block hero-list-comp-add-hero block hero-list-comp-add-hero
:marked :marked
Back in the `HeroListComponent`, we see that *its* `addHero()` method subscribes to the observable returned by the *service's* `addHero()` method. Back in the `HeroListComponent`, we see that *its* `addHero()` method subscribes to the observable returned by the *service's* `addHero()` method.
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=".")
block promises block promises
a#promises a#promises
@ -388,9 +426,9 @@ block promises
:marked :marked
Let's rewrite the `HeroService` using promises , highlighting just the parts that are different. Let's rewrite the `HeroService` using promises , highlighting just the parts that are different.
+makeTabs( +makeTabs(
'server-communication/ts/app/toh/hero.service.1.ts,server-communication/ts/app/toh/hero.service.ts', 'server-communication/ts/app/toh/hero.service.promise.ts,server-communication/ts/app/toh/hero.service.ts',
'methods, methods', 'methods, methods',
'app/toh/hero.service.ts (promise-based), app/toh/hero.service.ts (observable-based)') 'app/toh/hero.service.promise.ts (promise-based), app/toh/hero.service.ts (observable-based)')
:marked :marked
Converting from an observable to a promise is as simple as calling `toPromise(success, fail)`. Converting from an observable to a promise is as simple as calling `toPromise(success, fail)`.
@ -405,9 +443,9 @@ block promises
We have to adjust the calling component to expect a `Promise` instead of an `Observable`. We have to adjust the calling component to expect a `Promise` instead of an `Observable`.
+makeTabs( +makeTabs(
'server-communication/ts/app/toh/hero-list.component.1.ts, server-communication/ts/app/toh/hero-list.component.ts', 'server-communication/ts/app/toh/hero-list.component.promise.ts, server-communication/ts/app/toh/hero-list.component.ts',
'methods, methods', 'methods, methods',
'app/toh/hero-list.component.ts (promise-based), app/toh/hero-list.component.ts (observable-based)') 'app/toh/hero-list.component.promise.ts (promise-based), app/toh/hero-list.component.ts (observable-based)')
:marked :marked
The only obvious difference is that we call `then` on the returned promise instead of `subscribe`. The only obvious difference is that we call `then` on the returned promise instead of `subscribe`.
We give both methods the same functional arguments. We give both methods the same functional arguments.
@ -457,7 +495,7 @@ figure.image-display
block wikipedia-jsonp+ block wikipedia-jsonp+
:marked :marked
Wikipedia offers both `CORS` and `JSONP` search APIs, let's use the latter for this example. Wikipedia offers a modern `CORS` API and a legacy `JSONP` search API. 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.
@ -637,7 +675,7 @@ a#in-mem-web-api
method that returns a map whose keys are collection names and whose values method that returns a map whose keys are collection names and whose values
are #{_array}s 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 based on 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
Ensure that the `HeroService` endpoint refers to the web API: Ensure that the `HeroService` endpoint refers to the web API:
@ -650,13 +688,10 @@ block redirect-to-web-api
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 techniques.
in `TohComponent`. We initialize the in-memory web API with mock hero data at the same time. We initialize the in-memory web API with *seed data* from the mock hero dataset at the same time.
Here are the pertinent details, excerpt from `TohComponent`, starting with the imports: p Here is the revised (and final) version of the #[code #[+adjExPath('app/main.ts')]] demonstrating these steps.
+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/main.ts', 'final', 'app/main.ts (final)')(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=".")
p See the full source code in the #[+liveExampleLink2('live example', 'server-communication')]. p See the full source code in the #[+liveExampleLink2('live example', 'server-communication')].