parent
a7826ee44c
commit
8a5bcb4da7
|
@ -18,34 +18,37 @@ import {HeroService} from './hero.service.1';
|
||||||
<button (click)="addHero(newHero.value); newHero.value=''">
|
<button (click)="addHero(newHero.value); newHero.value=''">
|
||||||
Add Hero
|
Add Hero
|
||||||
</button>
|
</button>
|
||||||
|
<div class="error" *ngIf="errorMessage">{{errorMessage}}</div>
|
||||||
`,
|
`,
|
||||||
// #enddocregion template
|
// #enddocregion template
|
||||||
|
styles: ['.error {color:red;}']
|
||||||
})
|
})
|
||||||
// #docregion component
|
// #docregion component
|
||||||
export class HeroListComponent implements OnInit {
|
export class HeroListComponent implements OnInit {
|
||||||
|
|
||||||
constructor (private _heroService: HeroService) {}
|
constructor (private _heroService: HeroService) {}
|
||||||
|
|
||||||
|
errorMessage: string;
|
||||||
heroes:Hero[];
|
heroes:Hero[];
|
||||||
|
|
||||||
// #docregion ngOnInit
|
ngOnInit() { this.getHeroes(); }
|
||||||
ngOnInit() {
|
|
||||||
|
// #docregion methods
|
||||||
|
getHeroes() {
|
||||||
this._heroService.getHeroes()
|
this._heroService.getHeroes()
|
||||||
.then(
|
.then(
|
||||||
heroes => this.heroes = heroes,
|
heroes => this.heroes = heroes,
|
||||||
error => alert(`Server error. Try again later`));
|
error => this.errorMessage = <any>error);
|
||||||
}
|
}
|
||||||
// #enddocregion ngOnInit
|
|
||||||
|
|
||||||
// #docregion addHero
|
|
||||||
addHero (name: string) {
|
addHero (name: string) {
|
||||||
if (!name) {return;}
|
if (!name) {return;}
|
||||||
this._heroService.addHero(name)
|
this._heroService.addHero(name)
|
||||||
.then(
|
.then(
|
||||||
hero => this.heroes.push(hero),
|
hero => this.heroes.push(hero),
|
||||||
error => alert(error));
|
error => this.errorMessage = <any>error);
|
||||||
|
|
||||||
}
|
}
|
||||||
// #enddocregion addHero
|
// #enddocregion methods
|
||||||
}
|
}
|
||||||
// #enddocregion component
|
// #enddocregion component
|
||||||
|
|
|
@ -17,23 +17,29 @@ import {HeroService} from './hero.service';
|
||||||
<button (click)="addHero(newHero.value); newHero.value=''">
|
<button (click)="addHero(newHero.value); newHero.value=''">
|
||||||
Add Hero
|
Add Hero
|
||||||
</button>
|
</button>
|
||||||
|
<div class="error" *ngIf="errorMessage">{{errorMessage}}</div>
|
||||||
`,
|
`,
|
||||||
|
styles: ['.error {color:red;}']
|
||||||
})
|
})
|
||||||
// #docregion component
|
// #docregion component
|
||||||
export class HeroListComponent implements OnInit {
|
export class HeroListComponent implements OnInit {
|
||||||
|
|
||||||
constructor (private _heroService: HeroService) {}
|
constructor (private _heroService: HeroService) {}
|
||||||
|
|
||||||
|
errorMessage: string;
|
||||||
heroes:Hero[];
|
heroes:Hero[];
|
||||||
|
|
||||||
// #docregion ngOnInit
|
ngOnInit() { this.getHeroes(); }
|
||||||
ngOnInit() {
|
|
||||||
|
// #docregion methods
|
||||||
|
// #docregion getHeroes
|
||||||
|
getHeroes() {
|
||||||
this._heroService.getHeroes()
|
this._heroService.getHeroes()
|
||||||
.subscribe(
|
.subscribe(
|
||||||
heroes => this.heroes = heroes,
|
heroes => this.heroes = heroes,
|
||||||
error => alert(`Server error. Try again later`));
|
error => this.errorMessage = <any>error);
|
||||||
}
|
}
|
||||||
// #enddocregion ngOnInit
|
// #enddocregion getHeroes
|
||||||
|
|
||||||
// #docregion addHero
|
// #docregion addHero
|
||||||
addHero (name: string) {
|
addHero (name: string) {
|
||||||
|
@ -41,9 +47,10 @@ export class HeroListComponent implements OnInit {
|
||||||
this._heroService.addHero(name)
|
this._heroService.addHero(name)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
hero => this.heroes.push(hero),
|
hero => this.heroes.push(hero),
|
||||||
error => alert(error));
|
error => this.errorMessage = <any>error);
|
||||||
|
|
||||||
}
|
}
|
||||||
// #enddocregion addHero
|
// #enddocregion addHero
|
||||||
|
// #enddocregion methods
|
||||||
}
|
}
|
||||||
// #enddocregion component
|
// #enddocregion component
|
||||||
|
|
|
@ -2,36 +2,36 @@
|
||||||
// #docplaster
|
// #docplaster
|
||||||
|
|
||||||
// #docregion
|
// #docregion
|
||||||
// #docregion v1
|
import {Injectable} from 'angular2/core';
|
||||||
import {Injectable} from 'angular2/core';
|
import {Http, Response} from 'angular2/http';
|
||||||
import {Http} from 'angular2/http';
|
import {Hero} from './hero';
|
||||||
import {Hero} from './hero';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class HeroService {
|
export class HeroService {
|
||||||
constructor (private http: Http) {}
|
constructor (private http: Http) {}
|
||||||
|
|
||||||
private _heroesUrl = 'app/heroes.json';
|
private _heroesUrl = 'app/heroes';
|
||||||
|
|
||||||
|
// #docregion methods
|
||||||
getHeroes () {
|
getHeroes () {
|
||||||
// TODO: Error handling
|
|
||||||
// #docregion http-get
|
|
||||||
return this.http.get(this._heroesUrl)
|
return this.http.get(this._heroesUrl)
|
||||||
.toPromise()
|
.toPromise()
|
||||||
.then(res => <Hero[]> res.json().data);
|
.then(res => <Hero[]> res.json().data, this.handleError);
|
||||||
// #enddocregion http-get
|
|
||||||
}
|
}
|
||||||
// #enddocregion v1
|
|
||||||
|
|
||||||
// #docregion addhero
|
|
||||||
addHero (name: string) : Promise<Hero> {
|
addHero (name: string) : Promise<Hero> {
|
||||||
// TODO: Error handling
|
|
||||||
return this.http.post(this._heroesUrl, JSON.stringify({ name }))
|
return this.http.post(this._heroesUrl, JSON.stringify({ name }))
|
||||||
.toPromise()
|
.toPromise()
|
||||||
.then(res => <Hero> res.json().data);
|
.then(res => <Hero> res.json().data)
|
||||||
|
.catch(this.handleError);
|
||||||
}
|
}
|
||||||
// #enddocregion addhero
|
|
||||||
// #docregion v1
|
private handleError (error: any) {
|
||||||
|
// in a real world app, we may send the server to some remote logging infrastructure
|
||||||
|
// instead of just logging it to the console
|
||||||
|
console.error(error);
|
||||||
|
return Promise.reject(error.message || error.json().error || 'Server error');
|
||||||
|
}
|
||||||
|
// #enddocregion methods
|
||||||
}
|
}
|
||||||
// #enddocregion v1
|
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
|
|
|
@ -2,45 +2,48 @@
|
||||||
|
|
||||||
// #docregion
|
// #docregion
|
||||||
// #docregion v1
|
// #docregion v1
|
||||||
import {Injectable} from 'angular2/core';
|
import {Injectable} from 'angular2/core';
|
||||||
import {Http} from 'angular2/http';
|
import {Http, Response} from 'angular2/http';
|
||||||
import {Hero} from './hero';
|
import {Hero} from './hero';
|
||||||
import {Observable} from 'rxjs/Observable';
|
import {Observable} from 'rxjs/Observable';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class HeroService {
|
export class HeroService {
|
||||||
constructor (private http: Http) {}
|
constructor (private http: Http) {}
|
||||||
|
|
||||||
private _heroesUrl = 'app/heroes.json';
|
// #docregion endpoint
|
||||||
|
private _heroesUrl = 'app/heroes';
|
||||||
|
// #enddocregion endpoint
|
||||||
|
|
||||||
|
// #docregion methods
|
||||||
// #docregion error-handling
|
// #docregion error-handling
|
||||||
getHeroes () {
|
getHeroes () {
|
||||||
// #docregion http-get
|
// #docregion http-get
|
||||||
return this.http.get(this._heroesUrl)
|
return this.http.get(this._heroesUrl)
|
||||||
.map(res => <Hero[]> res.json().data)
|
.map(res => <Hero[]> res.json().data)
|
||||||
.catch(this.logAndPassOn);
|
.catch(this.handleError);
|
||||||
// #enddocregion http-get
|
// #enddocregion http-get
|
||||||
}
|
}
|
||||||
|
// #enddocregion error-handling
|
||||||
// #docregion logAndPassOn
|
// #enddocregion v1
|
||||||
private logAndPassOn (error: Error) {
|
|
||||||
// in a real world app, we may send the server to some remote logging infrastructure
|
|
||||||
// instead of just logging it to the console
|
|
||||||
console.error(error);
|
|
||||||
return Observable.throw(error);
|
|
||||||
}
|
|
||||||
// #enddocregion logAndPassOn
|
|
||||||
// #enddocregion error-handling
|
|
||||||
// #enddocregion v1
|
|
||||||
|
|
||||||
// #docregion addhero
|
// #docregion addhero
|
||||||
addHero (name: string) : Observable<Hero> {
|
addHero (name: string) : Observable<Hero> {
|
||||||
return this.http.post(this._heroesUrl, JSON.stringify({ name }))
|
return this.http.post(this._heroesUrl, JSON.stringify({ name }))
|
||||||
.map(res => <Hero> res.json().data)
|
.map(res => <Hero> res.json().data)
|
||||||
.catch(this.logAndPassOn)
|
.catch(this.handleError)
|
||||||
}
|
}
|
||||||
// #enddocregion addhero
|
// #enddocregion addhero
|
||||||
// #docregion v1
|
|
||||||
|
// #docregion v1
|
||||||
|
// #docregion error-handling
|
||||||
|
private handleError (error: Response) {
|
||||||
|
// in a real world app, we may send the server to some remote logging infrastructure
|
||||||
|
// instead of just logging it to the console
|
||||||
|
console.error(error);
|
||||||
|
return Observable.throw(error.json().error || 'Server error');
|
||||||
|
}
|
||||||
|
// #enddocregion error-handling
|
||||||
|
// #enddocregion methods
|
||||||
}
|
}
|
||||||
// #enddocregion v1
|
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
|
|
|
@ -5,5 +5,5 @@
|
||||||
"!**/*.js",
|
"!**/*.js",
|
||||||
"!**/*.[1].*"
|
"!**/*.[1].*"
|
||||||
],
|
],
|
||||||
"tags": ["http"]
|
"tags": ["http", "jsonp"]
|
||||||
}
|
}
|
|
@ -40,12 +40,12 @@ figure.image-display
|
||||||
Here is the `TohComponent` shell:
|
Here is the `TohComponent` shell:
|
||||||
+makeExample('server-communication/ts/app/toh/toh.component.ts', null, 'app/toh.component.ts')
|
+makeExample('server-communication/ts/app/toh/toh.component.ts', null, 'app/toh.component.ts')
|
||||||
:marked
|
:marked
|
||||||
As usual, we import the symbols we need. The newcomer is `HTTP_Providers`,
|
As usual, we import the symbols we need. The newcomer is `HTTP_PROVIDERS`,
|
||||||
an array of service providers from the Angular HTTP library.
|
an array of service providers from the Angular HTTP library.
|
||||||
We'll be using that library to access the server.
|
We'll be using that library to access the server.
|
||||||
We also import a `HeroService` that we'll look at shortly.
|
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,
|
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.
|
making them available to the child components of this "Tour of Heroes" application.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
|
@ -63,6 +63,8 @@ figure.image-display
|
||||||
value of the input box in the `(click)` event binding.
|
value of the input box in the `(click)` event binding.
|
||||||
When the user clicks the button, we pass that value to the component's `addHero` method and then
|
When the user clicks the button, we pass that value to the component's `addHero` method and then
|
||||||
clear it to make ready for a new hero name.
|
clear it to make ready for a new hero name.
|
||||||
|
|
||||||
|
Below the button is an optional error message.
|
||||||
|
|
||||||
### The `HeroListComponent` class
|
### The `HeroListComponent` class
|
||||||
We [inject](dependency-injection.html) the `HeroService` into the constructor.
|
We [inject](dependency-injection.html) the `HeroService` into the constructor.
|
||||||
|
@ -83,7 +85,11 @@ figure.image-display
|
||||||
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.
|
||||||
:marked
|
:marked
|
||||||
Now that the component is square away, we can turn to development of the backend data source
|
The service `get` and `addHero` methods return an `Observable` to which we `subscribe`,
|
||||||
|
specifying the actions to take if a method succeeds or fails.
|
||||||
|
We'll get to observables and subscription shortly.
|
||||||
|
|
||||||
|
With our basic intuitions about the component squared away, we can turn to development of the backend data source
|
||||||
and the client-side `HeroService` that talks to it.
|
and the client-side `HeroService` that talks to it.
|
||||||
|
|
||||||
### Fetching data
|
### Fetching data
|
||||||
|
@ -182,26 +188,35 @@ figure.image-display
|
||||||
|
|
||||||
### Always handle errors
|
### Always handle errors
|
||||||
|
|
||||||
The eagle-eyed reader may have spotted that we used the `catch` operator in conjunction with the `logAndPassOn` method that we
|
The eagle-eyed reader may have spotted our use of the `catch` operator in conjunction with a `handleError` method.
|
||||||
defined in our service. We haven't discussed so far how that actually works. Generally speaking, whenever we deal with I/O we have to account
|
We haven't discussed so far how that actually works.
|
||||||
for the cases where something goes wrong. It may just be an unreachable server or something more subtile.
|
Whenever we deal with I/O we must be prepared for something to go wrong as it surely will.
|
||||||
|
|
||||||
We could just ignore any errors in our `HeroService` and rely on the component to handle such cases. But it's often better to
|
We should catch errors in the `HeroService` and do something with them.
|
||||||
handle errors on both levels: Service and Component. In our case that may be as simple as logging the errors to the console at the service level
|
We may also pass an error message back to the component for presentation to the user
|
||||||
and blowing up an alert box in the user's face at the component level.
|
but only if we can say something the user can understand and act upon.
|
||||||
|
|
||||||
|
In this simple app we provide rudimentary error handling in both the service and the component.
|
||||||
|
|
||||||
The `catch` operator lets us do exactly that. This operator reacts to the error case of an Observable. It takes a function that can handle the error
|
We use the Observable `catch` operator on the service level.
|
||||||
and then return a new Observable to continue with. Because we like to keep things simple here, we just defined a function `logAndPassOn` that calls `console.error(error)` and returns a
|
It takes an error handling function with the failed `Response` object as the argument.
|
||||||
new Observable that re-emits the error with `Observable.throw(error)`.
|
Our service handler, `errorHandler`, logs the response to the console,
|
||||||
|
transforms the error into a user-friendly message, and returns the message in a new, failed observable via `Observable.throw`.
|
||||||
|
|
||||||
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'error-handling', 'app/toh/hero.service.ts')(format=".")
|
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'error-handling', 'app/toh/hero.service.ts')(format=".")
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Back in the `HeroListComponent` where we call `heroService.get`,
|
Back in the `HeroListComponent`, where we called `heroService.get`,
|
||||||
we pass a second parameter to the `subscribe` function to handle the error case at this level too.
|
we supply the `subscribe` function with a second function to handle the error message.
|
||||||
|
It sets an `errorMessage` variable which we've bound conditionally in the template.
|
||||||
|
|
||||||
+makeExample('server-communication/ts/app/toh/hero-list.component.ts', 'ngOnInit', 'app/toh/hero-list.component.ts (ngOnInit)')
|
+makeExample('server-communication/ts/app/toh/hero-list.component.ts', 'getHeroes', 'app/toh/hero-list.component.ts (getHeroes)')(format=".")
|
||||||
|
|
||||||
|
.l-sub-section
|
||||||
|
:marked
|
||||||
|
Try messing with the value of the api endpoint in the `HeroService` and watch it fail
|
||||||
|
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'endpoint', 'app/toh/hero.service.ts (endpoint)')
|
||||||
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
### Sending data to the server
|
### Sending data to the server
|
||||||
|
@ -242,6 +257,51 @@ code-example(format="." language="javascript").
|
||||||
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=".")
|
||||||
|
|
||||||
|
:marked
|
||||||
|
## Falling back to Promises
|
||||||
|
|
||||||
|
Although the Angular `http` client API returns an `Observable` we can turn it into a
|
||||||
|
[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) if we prefer.
|
||||||
|
It's easy to do and a promise-based version looks much like the observable-based version in simple cases.
|
||||||
|
.l-sub-section
|
||||||
|
:marked
|
||||||
|
While promises may be more familiar, observables have many advantages.
|
||||||
|
Don't rush to promises until you've observables a chance.
|
||||||
|
:marked
|
||||||
|
Let's rewrite the `HeroService` using promises , highlighting just the parts that are different.
|
||||||
|
+makeTabs(
|
||||||
|
'server-communication/ts/app/toh/hero.service.1.ts,server-communication/ts/app/toh/hero.service.ts',
|
||||||
|
'methods, methods',
|
||||||
|
'app/toh/hero.service.ts (promise-based), app/toh/hero.service.ts (observable-based)')
|
||||||
|
:marked
|
||||||
|
Converting from an observable to a promise is as simple as calling `toPromise(success, fail)`.
|
||||||
|
|
||||||
|
We move the observable's `map` callback to the first *success* parameter and its `catch` callback to the second *fail* parameter
|
||||||
|
and we're done!
|
||||||
|
Or we can follow the promise `then.catch` pattern as we do in the second `addHero` example.
|
||||||
|
|
||||||
|
Our `errorHandler` forwards an error message as a failed promise instead of a failed Observable.
|
||||||
|
|
||||||
|
We have to adjust the calling component to expect a `Promise` instead of an `Observable`.
|
||||||
|
|
||||||
|
+makeTabs(
|
||||||
|
'server-communication/ts/app/toh/hero-list.component.1.ts, server-communication/ts/app/toh/hero-list.component.ts',
|
||||||
|
'methods, methods',
|
||||||
|
'app/toh/hero-list.component.ts (promise-based), app/toh/hero-list.component.ts (observable-based)')
|
||||||
|
:marked
|
||||||
|
The only obvious difference is that we call `then` on the returned promise instead of `subscribe`.
|
||||||
|
We give both methods the same functional arguments.
|
||||||
|
.l-sub-section
|
||||||
|
:marked
|
||||||
|
The less obvious but critical difference is that these two methods return very different results!
|
||||||
|
|
||||||
|
The promise-based `then` returns another promise. We can keep chaining more `then` and `catch` calls, getting a new promise each time.
|
||||||
|
|
||||||
|
The `subscribe` method returns a `Subscription`. A `Subscription` is not another observable.
|
||||||
|
It's the end of the line for observables. We can't call `map` on it or call `subscribe` again.
|
||||||
|
The `Subscription` object has a different purpose, signified by its primary method, `unsubscribe`.
|
||||||
|
|
||||||
|
Learn more about observables to understand the implications and consequences of subscriptions.
|
||||||
:marked
|
:marked
|
||||||
## Communication with `JSONP`
|
## Communication with `JSONP`
|
||||||
|
|
||||||
|
@ -409,4 +469,4 @@ code-example.
|
||||||
+makeExample('server-communication/ts/app/toh/toh.component.ts', 'in-mem-web-api-providers', 'toh.component.ts (web api providers')(format=".")
|
+makeExample('server-communication/ts/app/toh/toh.component.ts', 'in-mem-web-api-providers', 'toh.component.ts (web api providers')(format=".")
|
||||||
:marked
|
:marked
|
||||||
See the full source code in the [live example](/resources/live-examples/server-communication/ts/plnkr.html).
|
See the full source code in the [live example](/resources/live-examples/server-communication/ts/plnkr.html).
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue