parent
a7826ee44c
commit
8a5bcb4da7
|
@ -18,34 +18,37 @@ import {HeroService} from './hero.service.1';
|
|||
<button (click)="addHero(newHero.value); newHero.value=''">
|
||||
Add Hero
|
||||
</button>
|
||||
<div class="error" *ngIf="errorMessage">{{errorMessage}}</div>
|
||||
`,
|
||||
// #enddocregion template
|
||||
styles: ['.error {color:red;}']
|
||||
})
|
||||
// #docregion component
|
||||
export class HeroListComponent implements OnInit {
|
||||
|
||||
constructor (private _heroService: HeroService) {}
|
||||
|
||||
errorMessage: string;
|
||||
heroes:Hero[];
|
||||
|
||||
// #docregion ngOnInit
|
||||
ngOnInit() {
|
||||
ngOnInit() { this.getHeroes(); }
|
||||
|
||||
// #docregion methods
|
||||
getHeroes() {
|
||||
this._heroService.getHeroes()
|
||||
.then(
|
||||
heroes => this.heroes = heroes,
|
||||
error => alert(`Server error. Try again later`));
|
||||
error => this.errorMessage = <any>error);
|
||||
}
|
||||
// #enddocregion ngOnInit
|
||||
|
||||
// #docregion addHero
|
||||
addHero (name: string) {
|
||||
if (!name) {return;}
|
||||
this._heroService.addHero(name)
|
||||
.then(
|
||||
hero => this.heroes.push(hero),
|
||||
error => alert(error));
|
||||
error => this.errorMessage = <any>error);
|
||||
|
||||
}
|
||||
// #enddocregion addHero
|
||||
// #enddocregion methods
|
||||
}
|
||||
// #enddocregion component
|
||||
|
|
|
@ -17,23 +17,29 @@ import {HeroService} from './hero.service';
|
|||
<button (click)="addHero(newHero.value); newHero.value=''">
|
||||
Add Hero
|
||||
</button>
|
||||
<div class="error" *ngIf="errorMessage">{{errorMessage}}</div>
|
||||
`,
|
||||
styles: ['.error {color:red;}']
|
||||
})
|
||||
// #docregion component
|
||||
export class HeroListComponent implements OnInit {
|
||||
|
||||
constructor (private _heroService: HeroService) {}
|
||||
|
||||
errorMessage: string;
|
||||
heroes:Hero[];
|
||||
|
||||
// #docregion ngOnInit
|
||||
ngOnInit() {
|
||||
ngOnInit() { this.getHeroes(); }
|
||||
|
||||
// #docregion methods
|
||||
// #docregion getHeroes
|
||||
getHeroes() {
|
||||
this._heroService.getHeroes()
|
||||
.subscribe(
|
||||
heroes => this.heroes = heroes,
|
||||
error => alert(`Server error. Try again later`));
|
||||
error => this.errorMessage = <any>error);
|
||||
}
|
||||
// #enddocregion ngOnInit
|
||||
// #enddocregion getHeroes
|
||||
|
||||
// #docregion addHero
|
||||
addHero (name: string) {
|
||||
|
@ -41,9 +47,10 @@ export class HeroListComponent implements OnInit {
|
|||
this._heroService.addHero(name)
|
||||
.subscribe(
|
||||
hero => this.heroes.push(hero),
|
||||
error => alert(error));
|
||||
error => this.errorMessage = <any>error);
|
||||
|
||||
}
|
||||
// #enddocregion addHero
|
||||
// #enddocregion methods
|
||||
}
|
||||
// #enddocregion component
|
||||
|
|
|
@ -2,36 +2,36 @@
|
|||
// #docplaster
|
||||
|
||||
// #docregion
|
||||
// #docregion v1
|
||||
import {Injectable} from 'angular2/core';
|
||||
import {Http} from 'angular2/http';
|
||||
import {Hero} from './hero';
|
||||
import {Injectable} from 'angular2/core';
|
||||
import {Http, Response} from 'angular2/http';
|
||||
import {Hero} from './hero';
|
||||
|
||||
@Injectable()
|
||||
export class HeroService {
|
||||
constructor (private http: Http) {}
|
||||
|
||||
private _heroesUrl = 'app/heroes.json';
|
||||
private _heroesUrl = 'app/heroes';
|
||||
|
||||
// #docregion methods
|
||||
getHeroes () {
|
||||
// TODO: Error handling
|
||||
// #docregion http-get
|
||||
return this.http.get(this._heroesUrl)
|
||||
.toPromise()
|
||||
.then(res => <Hero[]> res.json().data);
|
||||
// #enddocregion http-get
|
||||
.then(res => <Hero[]> res.json().data, this.handleError);
|
||||
}
|
||||
// #enddocregion v1
|
||||
|
||||
// #docregion addhero
|
||||
addHero (name: string) : Promise<Hero> {
|
||||
// TODO: Error handling
|
||||
return this.http.post(this._heroesUrl, JSON.stringify({ name }))
|
||||
.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
|
||||
|
|
|
@ -2,45 +2,48 @@
|
|||
|
||||
// #docregion
|
||||
// #docregion v1
|
||||
import {Injectable} from 'angular2/core';
|
||||
import {Http} from 'angular2/http';
|
||||
import {Hero} from './hero';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {Injectable} from 'angular2/core';
|
||||
import {Http, Response} from 'angular2/http';
|
||||
import {Hero} from './hero';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
|
||||
@Injectable()
|
||||
export class HeroService {
|
||||
constructor (private http: Http) {}
|
||||
|
||||
private _heroesUrl = 'app/heroes.json';
|
||||
// #docregion endpoint
|
||||
private _heroesUrl = 'app/heroes';
|
||||
// #enddocregion endpoint
|
||||
|
||||
// #docregion methods
|
||||
// #docregion error-handling
|
||||
getHeroes () {
|
||||
// #docregion http-get
|
||||
return this.http.get(this._heroesUrl)
|
||||
.map(res => <Hero[]> res.json().data)
|
||||
.catch(this.logAndPassOn);
|
||||
.catch(this.handleError);
|
||||
// #enddocregion http-get
|
||||
}
|
||||
|
||||
// #docregion logAndPassOn
|
||||
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
|
||||
// #enddocregion error-handling
|
||||
// #enddocregion v1
|
||||
|
||||
// #docregion addhero
|
||||
addHero (name: string) : Observable<Hero> {
|
||||
return this.http.post(this._heroesUrl, JSON.stringify({ name }))
|
||||
.map(res => <Hero> res.json().data)
|
||||
.catch(this.logAndPassOn)
|
||||
.catch(this.handleError)
|
||||
}
|
||||
// #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
|
||||
|
|
|
@ -5,5 +5,5 @@
|
|||
"!**/*.js",
|
||||
"!**/*.[1].*"
|
||||
],
|
||||
"tags": ["http"]
|
||||
"tags": ["http", "jsonp"]
|
||||
}
|
|
@ -40,12 +40,12 @@ figure.image-display
|
|||
Here is the `TohComponent` shell:
|
||||
+makeExample('server-communication/ts/app/toh/toh.component.ts', null, 'app/toh.component.ts')
|
||||
: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.
|
||||
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,
|
||||
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
|
||||
|
@ -64,6 +64,8 @@ figure.image-display
|
|||
When the user clicks the button, we pass that value to the component's `addHero` method and then
|
||||
clear it to make ready for a new hero name.
|
||||
|
||||
Below the button is an optional error message.
|
||||
|
||||
### The `HeroListComponent` class
|
||||
We [inject](dependency-injection.html) the `HeroService` into the constructor.
|
||||
That's the instance of the `HeroService` that we provided in the parent shell `TohComponent`.
|
||||
|
@ -83,7 +85,11 @@ figure.image-display
|
|||
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.
|
||||
: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.
|
||||
|
||||
### Fetching data
|
||||
|
@ -182,25 +188,34 @@ figure.image-display
|
|||
|
||||
### Always handle errors
|
||||
|
||||
The eagle-eyed reader may have spotted that we used the `catch` operator in conjunction with the `logAndPassOn` method that we
|
||||
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
|
||||
for the cases where something goes wrong. It may just be an unreachable server or something more subtile.
|
||||
The eagle-eyed reader may have spotted our use of the `catch` operator in conjunction with a `handleError` method.
|
||||
We haven't discussed so far how that actually works.
|
||||
Whenever we deal with I/O we must be prepared for something to go wrong as it surely will.
|
||||
|
||||
We could just ignore any errors in our `HeroService` and rely on the component to handle such cases. But it's often better to
|
||||
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
|
||||
and blowing up an alert box in the user's face at the component level.
|
||||
We should catch errors in the `HeroService` and do something with them.
|
||||
We may also pass an error message back to the component for presentation to the user
|
||||
but only if we can say something the user can understand and act upon.
|
||||
|
||||
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
|
||||
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
|
||||
new Observable that re-emits the error with `Observable.throw(error)`.
|
||||
In this simple app we provide rudimentary error handling in both the service and the component.
|
||||
|
||||
We use the Observable `catch` operator on the service level.
|
||||
It takes an error handling function with the failed `Response` object as the argument.
|
||||
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=".")
|
||||
|
||||
:marked
|
||||
Back in the `HeroListComponent` where we call `heroService.get`,
|
||||
we pass a second parameter to the `subscribe` function to handle the error case at this level too.
|
||||
Back in the `HeroListComponent`, where we called `heroService.get`,
|
||||
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
|
||||
|
@ -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.
|
||||
+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
|
||||
## Communication with `JSONP`
|
||||
|
||||
|
|
Loading…
Reference in New Issue