docs(server-communication): heavily refactored (TS & Dart)
This commit is contained in:
parent
a4bc455030
commit
29511831cd
|
@ -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 {}
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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');
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {}
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
*/
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
.error {color:red;}
|
|
@ -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'));
|
||||||
|
|
|
@ -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';
|
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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';
|
|
@ -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">
|
||||||
|
|
|
@ -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(); }
|
||||||
|
|
|
@ -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(); }
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) { }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
|
@ -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>
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
.error {color:red;}
|
|
@ -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=".")
|
|
||||||
|
|
|
@ -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 — 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` — just `map` and `catch`.
|
We don't need _all_ of these particular operators in the `HeroService` — 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')].
|
||||||
|
|
Loading…
Reference in New Issue