docs(server-communication): fix http after testing chapter discoveries

This commit is contained in:
Ward Bell 2016-04-13 08:21:32 -07:00
parent dd7b5176a8
commit 353abff084
12 changed files with 213 additions and 117 deletions

View File

@ -1,15 +1,21 @@
//#docregion // #docplaster
import {bootstrap} from 'angular2/platform/browser'; // #docregion
import { bootstrap } from 'angular2/platform/browser';
// #docregion http-providers
import { HTTP_PROVIDERS } from 'angular2/http';
// #enddocregion http-providers
// #docregion import-rxjs // #docregion import-rxjs
// Add all operators to Observable // Add all operators to Observable
import 'rxjs/Rx'; import 'rxjs/Rx';
// #enddocregion import-rxjs // #enddocregion import-rxjs
import {WikiComponent} from './wiki/wiki.component'; import { WikiComponent } from './wiki/wiki.component';
import {WikiSmartComponent} from './wiki/wiki-smart.component'; import { WikiSmartComponent } from './wiki/wiki-smart.component';
import {TohComponent} from './toh/toh.component'; import { TohComponent } from './toh/toh.component';
bootstrap(WikiComponent); bootstrap(WikiComponent);
bootstrap(WikiSmartComponent); bootstrap(WikiSmartComponent);
bootstrap(TohComponent); // #docregion http-providers
bootstrap(TohComponent, [HTTP_PROVIDERS]);
// #enddocregion http-providers

View File

@ -1,3 +1,4 @@
// ToH Promise Version
// #docregion // #docregion
import {Component, OnInit} from 'angular2/core'; import {Component, OnInit} from 'angular2/core';
import {Hero} from './hero'; import {Hero} from './hero';
@ -5,22 +6,7 @@ import {HeroService} from './hero.service.1';
@Component({ @Component({
selector: 'hero-list', selector: 'hero-list',
// #docregion template templateUrl: 'app/toh/hero-list.component.html',
template: `
<h3>Heroes:</h3>
<ul>
<li *ngFor="#hero of heroes">
{{ hero.name }}
</li>
</ul>
New Hero:
<input #newHero />
<button (click)="addHero(newHero.value); newHero.value=''">
Add Hero
</button>
<div class="error" *ngIf="errorMessage">{{errorMessage}}</div>
`,
// #enddocregion template
styles: ['.error {color:red;}'] styles: ['.error {color:red;}']
}) })
// #docregion component // #docregion component
@ -29,7 +15,7 @@ export class HeroListComponent implements OnInit {
constructor (private _heroService: HeroService) {} constructor (private _heroService: HeroService) {}
errorMessage: string; errorMessage: string;
heroes:Hero[]; heroes: Hero[];
ngOnInit() { this.getHeroes(); } ngOnInit() { this.getHeroes(); }

View File

@ -0,0 +1,13 @@
<!-- #docregion -->
<h3>Heroes:</h3>
<ul>
<li *ngFor="#hero of heroes">
{{ hero.name }}
</li>
</ul>
New Hero:
<input #newHero />
<button (click)="addHero(newHero.value); newHero.value=''">
Add Hero
</button>
<div class="error" *ngIf="errorMessage">{{errorMessage}}</div>

View File

@ -5,20 +5,7 @@ import {HeroService} from './hero.service';
@Component({ @Component({
selector: 'hero-list', selector: 'hero-list',
template: ` templateUrl: 'app/toh/hero-list.component.html',
<h3>Heroes:</h3>
<ul>
<li *ngFor="#hero of heroes">
{{ hero.name }}
</li>
</ul>
New Hero:
<input #newHero />
<button (click)="addHero(newHero.value); newHero.value=''">
Add Hero
</button>
<div class="error" *ngIf="errorMessage">{{errorMessage}}</div>
`,
styles: ['.error {color:red;}'] styles: ['.error {color:red;}']
}) })
// #docregion component // #docregion component

View File

@ -1,9 +1,9 @@
/* Promise version */ // ToH Promise Version
// #docplaster // #docplaster
// #docregion // #docregion
import {Injectable} from 'angular2/core'; import {Injectable} from 'angular2/core';
import {Http} from 'angular2/http'; import {Http, Response} from 'angular2/http';
import {Headers, RequestOptions} from 'angular2/http'; import {Headers, RequestOptions} from 'angular2/http';
import {Hero} from './hero'; import {Hero} from './hero';
@ -15,27 +15,36 @@ export class HeroService {
private _heroesUrl = 'app/heroes.json'; private _heroesUrl = 'app/heroes.json';
// #docregion methods // #docregion methods
getHeroes () { getHeroes (): Promise<Hero[]> {
return this.http.get(this._heroesUrl) return this.http.get(this._heroesUrl)
.toPromise() .toPromise()
.then(res => <Hero[]> res.json().data, this.handleError) .then(this.extractData)
.then(data => { console.log(data); return data; }); // eyeball results in the console .catch(this.handleError);
} }
addHero (name: string) : Promise<Hero> { addHero (name: string): Promise<Hero> {
let body = JSON.stringify({ name }); let body = JSON.stringify({ name });
let headers = new Headers({ 'Content-Type': 'application/json' }); let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers }); let options = new RequestOptions({ headers: headers });
return this.http.post(this._heroesUrl, body, options) return this.http.post(this._heroesUrl, body, options)
.toPromise() .toPromise()
.then(res => <Hero> res.json().data) .then(this.extractData)
.catch(this.handleError); .catch(this.handleError);
} }
private extractData(res: Response) {
if (res.status < 200 || res.status >= 300) {
throw new Error('Bad response status: ' + res.status);
}
let body = res.json();
return body.data || { };
}
private handleError (error: any) { private handleError (error: any) {
// in a real world app, we may send the error to some remote logging infrastructure // In a real world app, we might send the error to remote logging infrastructure
console.error(error); // log to console instead
let errMsg = error.message || 'Server error'; let errMsg = error.message || 'Server error';
console.error(errMsg); // log to console instead
return Promise.reject(errMsg); return Promise.reject(errMsg);
} }

View File

@ -3,7 +3,7 @@
// #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';
// #enddocregion v1 // #enddocregion v1
// #docregion import-request-options // #docregion import-request-options
import {Headers, RequestOptions} from 'angular2/http'; import {Headers, RequestOptions} from 'angular2/http';
@ -31,18 +31,13 @@ export class HeroService {
// #enddocregion endpoint // #enddocregion endpoint
// #docregion methods // #docregion methods
// #docregion error-handling // #docregion error-handling, http-get
getHeroes (): Observable<Hero[]> { getHeroes (): Observable<Hero[]> {
// #docregion http-get, http-get-v1
return this.http.get(this._heroesUrl) return this.http.get(this._heroesUrl)
.map(this.extractData) .map(this.extractData)
// #enddocregion v1, http-get-v1, error-handling
.do(data => console.log(data)) // eyeball results in the console
// #docregion v1, http-get-v1, error-handling
.catch(this.handleError); .catch(this.handleError);
// #enddocregion http-get, http-get-v1
} }
// #enddocregion error-handling // #enddocregion error-handling, http-get
// #enddocregion v1 // #enddocregion v1
// #docregion addhero // #docregion addhero
@ -62,6 +57,7 @@ export class HeroService {
// #docregion v1 // #docregion v1
// #docregion extract-data
private extractData(res: Response) { private extractData(res: Response) {
if (res.status < 200 || res.status >= 300) { if (res.status < 200 || res.status >= 300) {
throw new Error('Bad response status: ' + res.status); throw new Error('Bad response status: ' + res.status);
@ -69,12 +65,13 @@ export class HeroService {
let body = res.json(); let body = res.json();
return body.data || { }; return body.data || { };
} }
// #enddocregion extract-data
// #docregion error-handling // #docregion error-handling
private handleError (error: any) { private handleError (error: any) {
// in a real world app, we may send the error to some remote logging infrastructure // In a real world app, we might send the error to remote logging infrastructure
console.error(error); // log to console instead
let errMsg = error.message || 'Server error'; let errMsg = error.message || 'Server error';
console.error(errMsg); // log to console instead
return Observable.throw(errMsg); return Observable.throw(errMsg);
} }
// #enddocregion error-handling // #enddocregion error-handling

View File

@ -0,0 +1,32 @@
// ToH Promise Version
console.log ('Promise version');
import { Component } from 'angular2/core';
import { HTTP_PROVIDERS } from 'angular2/http';
import { HeroListComponent } from './hero-list.component.1';
import { HeroService } from './hero.service.1';
import { provide } from 'angular2/core';
import { XHRBackend } from 'angular2/http';
import { InMemoryBackendService,
SEED_DATA } from 'a2-in-memory-web-api/core';
import { HeroData } from '../hero-data';
@Component({
selector: 'my-toh',
template: `
<h1>Tour of Heroes</h1>
<hero-list></hero-list>
`,
directives:[HeroListComponent],
providers: [
HTTP_PROVIDERS,
HeroService,
// in-memory web api providers
provide(XHRBackend, { useClass: InMemoryBackendService }), // in-mem server
provide(SEED_DATA, { useClass: HeroData }) // in-mem server data
]
})
export class TohComponent { }

View File

@ -0,0 +1,44 @@
// #docplaster
// #docregion
import { Component } from 'angular2/core';
import { HTTP_PROVIDERS } from 'angular2/http';
import { HeroListComponent } from './hero-list.component';
import { HeroService } from './hero.service';
// #enddocregion
// #docregion in-mem-web-api-imports
import { provide } from 'angular2/core';
import { XHRBackend } from 'angular2/http';
// in-memory web api imports
import { InMemoryBackendService,
SEED_DATA } from 'a2-in-memory-web-api/core';
import { HeroData } from '../hero-data';
// #enddocregion in-mem-web-api-imports
// #docregion
@Component({
selector: 'my-toh',
// #docregion template
template: `
<h1>Tour of Heroes</h1>
<hero-list></hero-list>
`,
// #enddocregion template
directives: [HeroListComponent],
providers: [
HTTP_PROVIDERS,
HeroService,
// #enddocregion
// #docregion in-mem-web-api-providers
// in-memory web api providers
provide(XHRBackend, { useClass: InMemoryBackendService }), // in-mem server
provide(SEED_DATA, { useClass: HeroData }) // in-mem server data
// #enddocregion in-mem-web-api-providers
// #docregion
]
})
export class TohComponent { }
// #enddocregion

View File

@ -1,24 +1,23 @@
// #docplaster // #docplaster
// #docregion // #docregion
import {Component} from 'angular2/core'; import { Component } from 'angular2/core';
import {HTTP_PROVIDERS} from 'angular2/http'; import { HTTP_PROVIDERS } from 'angular2/http';
import {Hero} from './hero'; import { HeroListComponent } from './hero-list.component';
import {HeroListComponent} from './hero-list.component'; import { HeroService } from './hero.service';
import {HeroService} from './hero.service'; // #enddocregion
//#enddocregion
//#docregion in-mem-web-api-imports // #docregion in-mem-web-api-imports
import {provide} from 'angular2/core'; import { provide } from 'angular2/core';
import {XHRBackend} from 'angular2/http'; import { XHRBackend } from 'angular2/http';
// in-memory web api imports // in-memory web api imports
import {InMemoryBackendService, import { InMemoryBackendService,
SEED_DATA} from 'a2-in-memory-web-api/core'; SEED_DATA } from 'a2-in-memory-web-api/core';
import {HeroData} from '../hero-data'; import { HeroData } from '../hero-data';
// #enddocregion in-mem-web-api-imports // #enddocregion in-mem-web-api-imports
//#docregion // #docregion
@Component({ @Component({
selector: 'my-toh', selector: 'my-toh',
@ -28,17 +27,17 @@ import {HeroData} from '../hero-data';
<hero-list></hero-list> <hero-list></hero-list>
`, `,
// #enddocregion template // #enddocregion template
directives:[HeroListComponent], directives: [HeroListComponent],
providers: [ providers: [
HTTP_PROVIDERS, HTTP_PROVIDERS,
HeroService, HeroService,
//#enddocregion // #enddocregion
//#docregion in-mem-web-api-providers // #docregion in-mem-web-api-providers
// in-memory web api providers // in-memory web api providers
provide(XHRBackend, { useClass: InMemoryBackendService }), // in-mem server provide(XHRBackend, { useClass: InMemoryBackendService }), // in-mem server
provide(SEED_DATA, { useClass: HeroData }) // in-mem server data provide(SEED_DATA, { useClass: HeroData }) // in-mem server data
//#enddocregion in-mem-web-api-providers // #enddocregion in-mem-web-api-providers
//#docregion // #docregion
] ]
}) })
export class TohComponent { } export class TohComponent { }

View File

@ -36,7 +36,7 @@ export class HeroService {
} }
private handleError (error: any) { private handleError (error: any) {
// in a real world app, we may send the error to some remote logging infrastructure // In a real world app, we might send the error to remote logging infrastructure
let errMsg = error.message || 'Server error'; let errMsg = error.message || 'Server error';
console.error(errMsg); // log to console instead console.error(errMsg); // log to console instead
return Observable.throw(errMsg); return Observable.throw(errMsg);

View File

@ -21,7 +21,6 @@ include ../_util-fns
[Enabling RxJS Operators](#enable-rxjs-operators)<br> [Enabling RxJS Operators](#enable-rxjs-operators)<br>
[Extract JSON data with RxJS map](#map)<br> [Extract JSON data with RxJS map](#map)<br>
[Error handling](#error-handling)<br> [Error handling](#error-handling)<br>
[Log results to console](#do)<br>
[Send data to the server](#update)<br> [Send data to the server](#update)<br>
[Add headers](#headers)<br> [Add headers](#headers)<br>
[Promises instead of observables](#promises)<br> [Promises instead of observables](#promises)<br>
@ -67,14 +66,20 @@ figure.image-display
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
: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 :marked
Learn about providers in the [Dependency Injection](dependency-injection.html) chapter. Learn about providers in the [Dependency Injection](dependency-injection.html) chapter.
:marked :marked
This sample only has one child, the `HeroListComponent` shown here in full: This sample only has one child, the `HeroListComponent`. Here's its template:
+makeExample('server-communication/ts/app/toh/hero-list.component.ts', null, 'app/toh/hero-list.component.ts') +makeExample('server-communication/ts/app/toh/hero-list.component.html', null, 'app/toh/hero-list.component.html (Template)')
:marked :marked
The component template displays a list of heroes with the `NgFor` repeater directive. The component template displays a list of heroes with the `NgFor` repeater directive.
figure.image-display
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 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 [local template variable](template-syntax.html#local-vars), `newHero`, to access the We use a [local template variable](template-syntax.html#local-vars), `newHero`, to access the
@ -82,22 +87,26 @@ figure.image-display
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. Below the button is a (hidden) area for an error message.
a(id="oninit") a(id="oninit")
a(id="HeroListComponent") a(id="HeroListComponent")
:marked :marked
### The *HeroListComponent* class ### The *HeroListComponent* class
Here's the component class:
+makeExample('server-communication/ts/app/toh/hero-list.component.ts','component', 'app/toh/hero-list.component.ts (class)')
:marked
We [inject](dependency-injection.html) the `HeroService` into the constructor. 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`. That's the instance of the `HeroService` that we provided in the parent shell `TohComponent`.
Notice that the component **does not talk to the server directly!** Notice that the component **does not talk to the server directly!**
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). Those details it delegates to the `heroService` class (which we'll get to in a moment).
This is a golden rule: *always delegate data access to a supporting service class*.
Although the component should request heroes immediately, This is a golden rule: **always delegate data access to a supporting service class**.
we do **not** call the service `get` method in the component's constructor.
Although _at runtime_ the component requests heroes immediately after creation,
we do **not** call the service's `get` method in the component's constructor.
We call it inside the `ngOnInit` [lifecycle hook](lifecycle-hooks.html) instead We call it inside the `ngOnInit` [lifecycle hook](lifecycle-hooks.html) instead
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
@ -106,8 +115,9 @@ a(id="HeroListComponent")
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
The service `get` and `addHero` methods return an `Observable` of HTTP Responses to which we `subscribe`, The service `get` and `addHero` methods return an `Observable` of HTTP hero data.
specifying the actions to take if a method succeeds or fails. We subscribe to this `Observable`,
specifying the actions to take when the request succeeds or fails.
We'll get to observables and subscription shortly. We'll get to observables and subscription shortly.
With our basic intuitions about the component squared away, we can turn to development of the backend data source With our basic intuitions about the component squared away, we can turn to development of the backend data source
@ -122,7 +132,7 @@ a(id="HeroListComponent")
In this chapter, we get the heroes from the server using Angular's own HTTP Client service. In this chapter, we get the heroes from the server using Angular's own HTTP Client service.
Here's the new `HeroService`: Here's the new `HeroService`:
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'v1', 'app/toh/hero.service.ts')(format=".") +makeExample('server-communication/ts/app/toh/hero.service.ts', 'v1', 'app/toh/hero.service.ts')
:marked :marked
We begin by importing Angular's `Http` client service and We begin by importing Angular's `Http` client service and
[inject it](dependency-injection.html) into the `HeroService` constructor. [inject it](dependency-injection.html) into the `HeroService` constructor.
@ -133,7 +143,7 @@ a(id="HeroListComponent")
+makeExample('server-communication/ts/index.html', 'http', 'index.html')(format=".") +makeExample('server-communication/ts/index.html', 'http', 'index.html')(format=".")
:marked :marked
Look closely at how we call `http.get` Look closely at how we call `http.get`
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'http-get-v1', 'app/toh/hero.service.ts (http.get)')(format=".") +makeExample('server-communication/ts/app/toh/hero.service.ts', 'http-get', 'app/toh/hero.service.ts (getHeroes)')(format=".")
:marked :marked
We pass the resource URL to `get` and it calls the server which should return heroes. We pass the resource URL to `get` and it calls the server which should return heroes.
.l-sub-section .l-sub-section
@ -153,13 +163,6 @@ a(id="HeroListComponent")
In fact, the `http.get` method returns an **Observable** of HTTP Responses (`Observable<Response>`) from the RxJS library In fact, the `http.get` method returns an **Observable** of HTTP Responses (`Observable<Response>`) from the RxJS library
and `map` is one of the RxJS *operators*. and `map` is one of the RxJS *operators*.
.callout.is-important
header HTTP GET Delayed
:marked
The `http.get` does **not send the request just yet!** This observable is
[*cold*](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/creating.md#cold-vs-hot-observables)
which means the request won't go out until something *subscribes* to the observable.
That *something* is the [HeroListComponent](#subscribe).
.l-main-section .l-main-section
:marked :marked
@ -193,15 +196,37 @@ a(id="HeroListComponent")
It's best to add that statement early when we're bootstrapping the application. It's best to add that statement early when we're bootstrapping the application.
: :
+makeExample('server-communication/ts/app/main.ts', 'import-rxjs', 'app/main.ts (import rxjs)')(format=".") +makeExample('server-communication/ts/app/main.ts', 'import-rxjs', 'app/main.ts (import rxjs)')(format=".")
a(id="map")
a(id="extract-data")
:marked :marked
<a id="map"></a> ### Process the response object
### Map the response object Remember that our `getHeroes` method mapped the `http.get` response object to heroes with an `extractData` helper method:
Let's come back to the `HeroService` and look at the `http.get` call again to see why we needed `map()` +makeExample('server-communication/ts/app/toh/hero.service.ts', 'extract-data', 'app/toh/hero.service.ts (extractData)')(format=".")
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'http-get-v1', 'app/toh/hero.service.ts (http.get)')(format=".")
:marked :marked
The `response` object does not hold our data in a form we can use directly. The `response` object does not hold our data in a form we can use directly.
It takes an additional step &mdash; calling `response.json()` &mdash; to transform the bytes from the server into a JSON object. To make it useful in our application we must
* check for a bad response
* parse the response data into a JSON object
.alert.is-important
:marked
*Beta alert*: error status interception and parsing may be absorbed within `http` when Angular is released.
:marked
#### Bad status codes
A status code outside the 200-300 range is an error from the _application point of view_
but it is not an error from the _`http` point of view_.
For example, a `404 - Not Found` is a response like any other.
The request went out; a response came back; here it is, thank you very much.
We'd have an observable error only if `http` failed to operate (e.g., it errored internally).
Because a status code outside the 200-300 range _is an error_ from the application point of view,
we intercept it and throw, moving the observable chain to the error path.
The `catch` operator that is next in the `getHeroes` observable chain will handle our thrown error.
#### Parse to JSON
The response data are in JSON string form.
We must parse that string into JavaScript objects which we do by calling `response.json()`.
.l-sub-section .l-sub-section
:marked :marked
This is not Angular's own design. This is not Angular's own design.
@ -229,7 +254,17 @@ a(id="HeroListComponent")
It doesn't care where they come from. It doesn't care where they come from.
And it certainly doesn't want to deal with a response object. And it certainly doesn't want to deal with a response object.
<a id="error-handling"></a>
.callout.is-important
header HTTP GET is delayed
:marked
The `http.get` does **not send the request just yet!** This observable is
[*cold*](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/creating.md#cold-vs-hot-observables)
which means the request won't go out until something *subscribes* to the observable.
That *something* is the [HeroListComponent](#subscribe).
a(id="error-handling")
:marked
### Always handle errors ### Always handle errors
The eagle-eyed reader may have spotted our use of the `catch` operator in conjunction with a `handleError` method. The eagle-eyed reader may have spotted our use of the `catch` operator in conjunction with a `handleError` method.
@ -243,8 +278,8 @@ a(id="HeroListComponent")
In this simple app we provide rudimentary error handling in both the service and the component. In this simple app we provide rudimentary error handling in both the service and the component.
We use the Observable `catch` operator on the service level. We use the Observable `catch` operator on the service level.
It takes an error handling function with the failed `Response` object as the argument. It takes an error handling function with an error object as the argument.
Our service handler, `errorHandler`, logs the response to the console, Our service handler, `handleError`, logs the response to the console,
transforms the error into a user-friendly message, and returns the message in a new, failed observable via `Observable.throw`. transforms the error into a user-friendly message, and returns the message in a new, failed observable via `Observable.throw`.
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'error-handling', 'app/toh/hero.service.ts')(format=".") +makeExample('server-communication/ts/app/toh/hero.service.ts', 'error-handling', 'app/toh/hero.service.ts')(format=".")
@ -263,19 +298,7 @@ a(id="HeroListComponent")
.l-sub-section .l-sub-section
:marked :marked
Want to see it fail? Reset the api endpoint in the `HeroService` to a bad value. Remember to restore it! Want to see it fail? Reset the api endpoint in the `HeroService` to a bad value. Remember to restore it!
<a id="do"></a>
<a id="console-log"></a>
:marked
### Peek at results in the console
During development we're often curious about the data returned by the server.
Logging to console without disrupting the flow would be nice.
The Observable `do` operator is perfect for the job.
It passes the input through to the output while we do something with a useful side-effect such as writing to console.
Slip it into the pipeline between `map` and `catch` like this.
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'http-get', 'app/toh/hero.service.ts')(format=".")
:marked
Remember to comment it out before going to production!
<a id="update"></a> <a id="update"></a>
<a id="post"></a> <a id="post"></a>
@ -327,7 +350,7 @@ code-example(format="." language="javascript").
:marked :marked
### JSON results ### JSON results
As with `get`, we extract the data from the response with `json()` and unwrap the hero via the `data` property. As with `getHeroes`, we [extract the data](#extract-data) from the response with `json()` and unwrap the hero via the `data` property.
.alert.is-important .alert.is-important
:marked :marked
Know the shape of the data returned by the server. Know the shape of the data returned by the server.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 12 KiB