From c28b003a3c9ff3efff17015dd214d4598ad7c6d8 Mon Sep 17 00:00:00 2001 From: Ward Bell Date: Wed, 19 Oct 2016 23:17:50 -0700 Subject: [PATCH] docs(HTTP Client): edit copy to conform to G-doc guidelines. (#2630) --- .../ts/app/app.component.ts | 2 +- .../server-communication/ts/app/hero-data.ts | 8 +- .../server-communication/ts/app/heroes.json | 8 +- .../ts/app/rxjs-operators.ts | 2 +- .../ts/app/toh/hero-list.component.html | 15 +- .../ts/app/toh/hero-list.component.promise.ts | 3 +- .../ts/app/toh/hero-list.component.ts | 3 +- .../ts/app/toh/hero.service.promise.ts | 20 +- .../ts/app/toh/hero.service.ts | 20 +- .../ts/app/wiki/wiki-smart.component.ts | 36 +- .../ts/app/wiki/wiki.component.html | 11 + .../ts/app/wiki/wiki.component.ts | 20 +- .../server-communication/ts/index.html | 2 - .../server-communication/ts/sample.css | 1 - public/docs/ts/latest/guide/_data.json | 2 +- .../ts/latest/guide/server-communication.jade | 640 +++++++++--------- 16 files changed, 400 insertions(+), 393 deletions(-) create mode 100644 public/docs/_examples/server-communication/ts/app/wiki/wiki.component.html delete mode 100644 public/docs/_examples/server-communication/ts/sample.css diff --git a/public/docs/_examples/server-communication/ts/app/app.component.ts b/public/docs/_examples/server-communication/ts/app/app.component.ts index 3a532b6524..25ada8f907 100644 --- a/public/docs/_examples/server-communication/ts/app/app.component.ts +++ b/public/docs/_examples/server-communication/ts/app/app.component.ts @@ -3,7 +3,7 @@ import { Component } from '@angular/core'; // #docregion import-rxjs -// Add the RxJS Observable operators we need in this app. +// Add the RxJS Observable operators. import './rxjs-operators'; // #enddocregion import-rxjs diff --git a/public/docs/_examples/server-communication/ts/app/hero-data.ts b/public/docs/_examples/server-communication/ts/app/hero-data.ts index 393d52eef7..4db6aca115 100644 --- a/public/docs/_examples/server-communication/ts/app/hero-data.ts +++ b/public/docs/_examples/server-communication/ts/app/hero-data.ts @@ -3,10 +3,10 @@ import { InMemoryDbService } from 'angular-in-memory-web-api'; export class HeroData implements InMemoryDbService { createDb() { let heroes = [ - { id: '1', name: 'Windstorm' }, - { id: '2', name: 'Bombasto' }, - { id: '3', name: 'Magneta' }, - { id: '4', name: 'Tornado' } + { id: 1, name: 'Windstorm' }, + { id: 2, name: 'Bombasto' }, + { id: 3, name: 'Magneta' }, + { id: 4, name: 'Tornado' } ]; return {heroes}; } diff --git a/public/docs/_examples/server-communication/ts/app/heroes.json b/public/docs/_examples/server-communication/ts/app/heroes.json index c6b5cc91df..dfb589066b 100644 --- a/public/docs/_examples/server-communication/ts/app/heroes.json +++ b/public/docs/_examples/server-communication/ts/app/heroes.json @@ -1,8 +1,8 @@ { "data": [ - { "id": "1", "name": "Windstorm" }, - { "id": "2", "name": "Bombasto" }, - { "id": "3", "name": "Magneta" }, - { "id": "4", "name": "Tornado" } + { "id": 1, "name": "Windstorm" }, + { "id": 2, "name": "Bombasto" }, + { "id": 3, "name": "Magneta" }, + { "id": 4, "name": "Tornado" } ] } diff --git a/public/docs/_examples/server-communication/ts/app/rxjs-operators.ts b/public/docs/_examples/server-communication/ts/app/rxjs-operators.ts index 6f8d626267..0a9b9fcc88 100644 --- a/public/docs/_examples/server-communication/ts/app/rxjs-operators.ts +++ b/public/docs/_examples/server-communication/ts/app/rxjs-operators.ts @@ -2,7 +2,7 @@ // 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. +// Import just the rxjs statics and operators needed for THIS app. // Statics import 'rxjs/add/observable/throw'; diff --git a/public/docs/_examples/server-communication/ts/app/toh/hero-list.component.html b/public/docs/_examples/server-communication/ts/app/toh/hero-list.component.html index 36666f2ed3..65ca9cfbb7 100644 --- a/public/docs/_examples/server-communication/ts/app/toh/hero-list.component.html +++ b/public/docs/_examples/server-communication/ts/app/toh/hero-list.component.html @@ -2,13 +2,10 @@

Tour of Heroes ({{mode}})

Heroes:

-New hero name: - - -
{{errorMessage}}
+ + + + +

{{errorMessage}}

diff --git a/public/docs/_examples/server-communication/ts/app/toh/hero-list.component.promise.ts b/public/docs/_examples/server-communication/ts/app/toh/hero-list.component.promise.ts index f6306f1e03..cde90d34bf 100644 --- a/public/docs/_examples/server-communication/ts/app/toh/hero-list.component.promise.ts +++ b/public/docs/_examples/server-communication/ts/app/toh/hero-list.component.promise.ts @@ -8,7 +8,8 @@ import { HeroService } from './hero.service.promise'; selector: 'hero-list-promise', moduleId: module.id, templateUrl: 'hero-list.component.html', - providers: [ HeroService ] + providers: [ HeroService ], + styles: ['.error {color:red;}'] }) // #docregion component export class HeroListPromiseComponent implements OnInit { diff --git a/public/docs/_examples/server-communication/ts/app/toh/hero-list.component.ts b/public/docs/_examples/server-communication/ts/app/toh/hero-list.component.ts index 0fa7bbad6e..f52144dcff 100644 --- a/public/docs/_examples/server-communication/ts/app/toh/hero-list.component.ts +++ b/public/docs/_examples/server-communication/ts/app/toh/hero-list.component.ts @@ -8,7 +8,8 @@ import { HeroService } from './hero.service'; moduleId: module.id, selector: 'hero-list', templateUrl: 'hero-list.component.html', - providers: [ HeroService ] + providers: [ HeroService ], + styles: ['.error {color:red;}'] }) // #docregion component export class HeroListComponent implements OnInit { diff --git a/public/docs/_examples/server-communication/ts/app/toh/hero.service.promise.ts b/public/docs/_examples/server-communication/ts/app/toh/hero.service.promise.ts index 6992eb5b6e..58fbec8e29 100644 --- a/public/docs/_examples/server-communication/ts/app/toh/hero.service.promise.ts +++ b/public/docs/_examples/server-communication/ts/app/toh/hero.service.promise.ts @@ -9,7 +9,7 @@ import { Hero } from './hero'; @Injectable() export class HeroService { // URL to web api - private heroesUrl = 'app/heroes.json'; + private heroesUrl = 'app/heroes'; constructor (private http: Http) {} @@ -22,11 +22,10 @@ export class HeroService { } addHero (name: string): Promise { - let body = JSON.stringify({ name }); let headers = new Headers({ 'Content-Type': 'application/json' }); let options = new RequestOptions({ headers: headers }); - return this.http.post(this.heroesUrl, body, options) + return this.http.post(this.heroesUrl, { name }, options) .toPromise() .then(this.extractData) .catch(this.handleError); @@ -37,12 +36,17 @@ export class HeroService { return body.data || { }; } - private handleError (error: any) { + private handleError (error: Response | any) { // 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 - let errMsg = (error.message) ? error.message : - error.status ? `${error.status} - ${error.statusText}` : 'Server error'; - console.error(errMsg); // log to console instead + let errMsg: string; + if (error instanceof Response) { + const body = error.json() || ''; + const err = body.error || JSON.stringify(body); + errMsg = `${error.status} - ${error.statusText || ''} ${err}`; + } else { + errMsg = error.message ? error.message : error.toString(); + } + console.error(errMsg); return Promise.reject(errMsg); } diff --git a/public/docs/_examples/server-communication/ts/app/toh/hero.service.ts b/public/docs/_examples/server-communication/ts/app/toh/hero.service.ts index 9e970843ea..c87e70b4b8 100644 --- a/public/docs/_examples/server-communication/ts/app/toh/hero.service.ts +++ b/public/docs/_examples/server-communication/ts/app/toh/hero.service.ts @@ -34,11 +34,10 @@ export class HeroService { // #docregion addhero, addhero-sig addHero (name: string): Observable { // #enddocregion addhero-sig - let body = JSON.stringify({ name }); let headers = new Headers({ 'Content-Type': 'application/json' }); let options = new RequestOptions({ headers: headers }); - return this.http.post(this.heroesUrl, body, options) + return this.http.post(this.heroesUrl, { name }, options) .map(this.extractData) .catch(this.handleError); } @@ -50,14 +49,19 @@ export class HeroService { return body.data || { }; } // #enddocregion extract-data - // #docregion error-handling - private handleError (error: any) { + + private handleError (error: Response | any) { // 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 - let errMsg = (error.message) ? error.message : - error.status ? `${error.status} - ${error.statusText}` : 'Server error'; - console.error(errMsg); // log to console instead + let errMsg: string; + if (error instanceof Response) { + const body = error.json() || ''; + const err = body.error || JSON.stringify(body); + errMsg = `${error.status} - ${error.statusText || ''} ${err}`; + } else { + errMsg = error.message ? error.message : error.toString(); + } + console.error(errMsg); return Observable.throw(errMsg); } // #enddocregion error-handling, methods diff --git a/public/docs/_examples/server-communication/ts/app/wiki/wiki-smart.component.ts b/public/docs/_examples/server-communication/ts/app/wiki/wiki-smart.component.ts index e6bbce139b..97ca33e1c0 100644 --- a/public/docs/_examples/server-communication/ts/app/wiki/wiki-smart.component.ts +++ b/public/docs/_examples/server-communication/ts/app/wiki/wiki-smart.component.ts @@ -1,3 +1,5 @@ +/* tslint:disable: member-ordering forin */ +// #docplaster // #docregion import { Component } from '@angular/core'; import { Observable } from 'rxjs/Observable'; @@ -8,33 +10,27 @@ import { Subject } from 'rxjs/Subject'; import { WikipediaService } from './wikipedia.service'; @Component({ + moduleId: module.id, selector: 'my-wiki-smart', - template: ` -

Smarter Wikipedia Demo

-

Fetches when typing stops

- - - -
    -
  • {{item}}
  • -
- `, - providers: [WikipediaService] + templateUrl: 'wiki.component.html', + providers: [ WikipediaService ] }) export class WikiSmartComponent { - - constructor (private wikipediaService: WikipediaService) { } + title = 'Smarter Wikipedia Demo'; + fetches = 'Fetches when typing stops'; + items: Observable; // #docregion subject private searchTermStream = new Subject(); - search(term: string) { this.searchTermStream.next(term); } // #enddocregion subject - // #docregion observable-operators - items: Observable = this.searchTermStream - .debounceTime(300) - .distinctUntilChanged() - .switchMap((term: string) => this.wikipediaService.search(term)); -// #enddocregion observable-operators + constructor (private wikipediaService: WikipediaService) { + // #docregion observable-operators + this.items = this.searchTermStream + .debounceTime(300) + .distinctUntilChanged() + .switchMap((term: string) => this.wikipediaService.search(term)); + // #enddocregion observable-operators + } } diff --git a/public/docs/_examples/server-communication/ts/app/wiki/wiki.component.html b/public/docs/_examples/server-communication/ts/app/wiki/wiki.component.html new file mode 100644 index 0000000000..e94d37f3aa --- /dev/null +++ b/public/docs/_examples/server-communication/ts/app/wiki/wiki.component.html @@ -0,0 +1,11 @@ + +

{{title}}

+

{{fetches}}

+ + + + + +
    +
  • {{item}}
  • +
diff --git a/public/docs/_examples/server-communication/ts/app/wiki/wiki.component.ts b/public/docs/_examples/server-communication/ts/app/wiki/wiki.component.ts index 92c31afb80..f72f005460 100644 --- a/public/docs/_examples/server-communication/ts/app/wiki/wiki.component.ts +++ b/public/docs/_examples/server-communication/ts/app/wiki/wiki.component.ts @@ -5,25 +5,19 @@ import { Observable } from 'rxjs/Observable'; import { WikipediaService } from './wikipedia.service'; @Component({ + moduleId: module.id, selector: 'my-wiki', - template: ` -

Wikipedia Demo

-

Fetches after each keystroke

- - - -
    -
  • {{item}}
  • -
- `, - providers: [WikipediaService] + templateUrl: 'wiki.component.html', + providers: [ WikipediaService ] }) export class WikiComponent { + title = 'Wikipedia Demo'; + fetches = 'Fetches after each keystroke'; items: Observable; - constructor (private wikipediaService: WikipediaService) {} - search (term: string) { this.items = this.wikipediaService.search(term); } + + constructor (private wikipediaService: WikipediaService) { } } diff --git a/public/docs/_examples/server-communication/ts/index.html b/public/docs/_examples/server-communication/ts/index.html index d265bef2f9..6b99fc5242 100644 --- a/public/docs/_examples/server-communication/ts/index.html +++ b/public/docs/_examples/server-communication/ts/index.html @@ -6,8 +6,6 @@ - - diff --git a/public/docs/_examples/server-communication/ts/sample.css b/public/docs/_examples/server-communication/ts/sample.css deleted file mode 100644 index 3bb281e096..0000000000 --- a/public/docs/_examples/server-communication/ts/sample.css +++ /dev/null @@ -1 +0,0 @@ -.error {color:red;} diff --git a/public/docs/ts/latest/guide/_data.json b/public/docs/ts/latest/guide/_data.json index 709b3985b3..87cd58f9e7 100644 --- a/public/docs/ts/latest/guide/_data.json +++ b/public/docs/ts/latest/guide/_data.json @@ -108,7 +108,7 @@ "server-communication": { "title": "HTTP Client", - "intro": "Talk to a remote server with an HTTP Client." + "intro": "Use an HTTP Client to talk to a remote server." }, "lifecycle-hooks": { diff --git a/public/docs/ts/latest/guide/server-communication.jade b/public/docs/ts/latest/guide/server-communication.jade index 9e7240ee15..0c5e268645 100644 --- a/public/docs/ts/latest/guide/server-communication.jade +++ b/public/docs/ts/latest/guide/server-communication.jade @@ -9,119 +9,119 @@ block includes .l-sub-section :marked The [`WebSocket`](https://tools.ietf.org/html/rfc6455) protocol is another important communication technology; - we won't cover it in this chapter. + it isn't covered in this page. :marked - Modern browsers support two HTTP-based APIs: - [XMLHttpRequest (XHR)](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) and + Modern browsers support two HTTP-based APIs: + [XMLHttpRequest (XHR)](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) and [JSONP](https://en.wikipedia.org/wiki/JSONP). A few browsers also support - [Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). - - The !{_Angular_http_library} simplifies application programming of the **XHR** and **JSONP** APIs - as we'll learn in this chapter covering: + [Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). - - [HTTP client sample overview](#http-client) - - [Fetch data with http.get](#fetch-data) -
  • [RxJS Observable of HTTP Responses](#rxjs)
  • -
  • [Enabling RxJS Operators](#enable-rxjs-operators)
  • - - [Extract JSON data](#extract-data) - - [Error handling](#error-handling) - - [Send data to the server](#update) -
  • [Promises instead of observables](#promises)
  • - - [Cross-origin requests: Wikipedia example](#cors) -
      -
    • [Set query string parameters](#search-parameters)
    • -
    • [Debounce search term input](#more-observables)
    • + The !{_Angular_http_library} simplifies application programming with the **XHR** and **JSONP** APIs. + This page covers: + + - [The Tour of Heroes *HTTP* client demo](#http-client). + - [Fetch data with http.get](#fetch-data). +
    • [RxJS library](#rxjs).
    • +
    • [Enable RxJS operators](#enable-rxjs-operators).
    • + - [Process the response object](#extract-data). + - [Always handle errors](#error-handling). + - [Send data to the server](#update). +
    • [Fall back to promises](#promises).
    • + - [Cross-origin requests: Wikipedia example](#cors). +
        +
      • [Search parameters](#search-parameters).
      • +
      • [More fun with observables](#more-observables).
      - - [Appendix: the in-memory web api service](#in-mem-web-api) + - [Appendix: Tour of Heroes in-memory server](#in-mem-web-api). + + A live example illustrates these topics. - We illustrate these topics with code that you can run live. - .l-main-section :marked # Demos - This chapter describes server communication with the help of the following demos + This page describes server communication with the help of the following demos: block demos-list :marked - - [HTTP client: Tour of Heroes with Observables](#http-client) - - [HTTP client: Tour of Heroes with !{_Promise}s](#promises) - - [JSONP client: Wikipedia to fetch data from a service that does not support CORS](#cors) - - [JSONP client: Wikipedia using observable operators to reduce server calls](#more-observables) + - [The Tour of Heroes *HTTP* client demo](#http-client). + - [Fall back to !{_Promise}s](#promises). + - [Cross-origin requests: Wikipedia example](#cors). + - [More fun with observables](#more-observables). :marked - These demos are orchestrated by the root `AppComponent` + The root `AppComponent` orchestrates these demos: +makeExample('server-communication/ts/app/app.component.ts', null, 'app/app.component.ts') +ifDocsFor('ts') :marked - There is nothing remarkable here _except_ for the import of RxJS operators. + There is nothing remarkable here _except_ for the import of RxJS operators, which is + described [later](#rxjs). +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#http-providers :marked - # Providing HTTP Services + # Providing HTTP services - We use the !{_Angular_Http} client to communicate with a server using a familiar HTTP request/response protocol. + First, configure the application to use server communication facilities. + + The !{_Angular_Http} client communicates with the server using a familiar HTTP request/response protocol. The `!{_Http}` client is one of a family of services in the !{_Angular_http_library}. +ifDocsFor('ts') .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. + When importing from the `@angular/http` module, SystemJS knows how to load services from + the !{_Angular_http_library} + because the `systemjs.config.js` file maps to that module name. :marked - Before we can use the `!{_Http}` client , we'll have to register it as a service provider with the Dependency Injection system. + Before you can use the `!{_Http}` client, you need 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. + Read about providers in the [Dependency Injection](dependency-injection.html) page. :marked - In this demo, we register providers by importing other NgModules to our root NgModule. + Register providers by importing other NgModules to the root NgModule in `app.module.ts`. +makeExample('server-communication/ts/app/app.module.1.ts', null, 'app/app.module.ts (v1)')(format='.') block http-providers :marked - We begin by importing the symbols we need, most of them familiar by now. - The newcomers are the `HttpModule` and the `JsonpModule` from the !{_Angular_http_library}. - - We add these modules to the application by passing them to the `imports` array in our root NgModule. + Begin by importing the necessary members. + The newcomers are the `HttpModule` and the `JsonpModule` from the !{_Angular_http_library}. For more information about imports and related terminology, see the [MDN reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) on the `import` statement. + + To add these modules to the application, pass them to the `imports` array in the root `@NgModule`. .l-sub-section :marked - We need the HttpModule to make HTTP calls. - We don't need the JsonpModule for plain HTTP. - We will demonstrate JSONP support later in this chapter. - We're loading its module now to save time. + The `HttpModule` is necessary for making HTTP calls. + Though the JsonpModule isn't necessary for plain HTTP, + there is a JSONP demo later in this page. + Loading its module now saves time. .l-main-section#http-client :marked - # The Tour of Heroes _HTTP_ Client Demo + # The Tour of Heroes HTTP client demo - Our first demo is 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. - We use the !{_Angular_Http} client to communicate via `XMLHttpRequest (XHR)`. + The first demo is 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 the user add new heroes, and saves them to the server. + The app uses the !{_Angular_Http} client to communicate via `XMLHttpRequest (XHR)`. - It works like this. + It works like this: figure.image-display img(src='/resources/images/devguide/server-communication/http-toh.gif' alt="ToH mini app" width="250") :marked This demo has a single component, the `HeroListComponent`. Here's its template: +makeExample('server-communication/ts/app/toh/hero-list.component.html', null, 'app/toh/hero-list.component.html (template)') :marked - It presents the list of heroes with an `ngFor`. - Below the list is an input box and an *Add Hero* button where we can enter the names of new heroes - and add them to the database. - We use a [template reference variable](template-syntax.html#ref-vars), `newHeroName`, to access the + It presents the list of heroes with an `ngFor`. + Below the list is an input box and an *Add Hero* button where you can enter the names of new heroes + and add them to the database. + A [template reference variable](template-syntax.html#ref-vars), `newHeroName`, accesses the 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 - clear it to make it ready for a new hero name. - + When the user clicks the button, that value passes to the component's `addHero` method and then + the event binding clears it to make it ready for a new hero name. + Below the button is an area for an error message. a#oninit @@ -129,51 +129,48 @@ a#HeroListComponent :marked ## 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)') ++makeExample('server-communication/ts/app/toh/hero-list.component.ts','component', 'app/toh/hero-list.component.ts (class)') :marked Angular [injects](dependency-injection.html) a `HeroService` into the constructor and the component calls that service to fetch and save data. - 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 **does not talk directly to the !{_Angular_Http} client**. + The component doesn't know or care how it gets the data. It delegates to the `HeroService`. - + This is a golden rule: **always delegate data access to a supporting service class**. - 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 - and count on Angular to call `ngOnInit` when it instantiates this component. + Although _at runtime_ the component requests heroes immediately after creation, + you **don't** call the service's `get` method in the component's constructor. + Instead, call it inside the `ngOnInit` [lifecycle hook](lifecycle-hooks.html) + and rely on Angular to call `ngOnInit` when it instantiates this component. .l-sub-section :marked - This is a *best practice*. - Components are easier to test and debug when their constructors are simple and all real work + This is a *best practice*. + 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. block getheroes-and-addhero :marked The service's `getHeroes()` and `addHero()` methods return an `Observable` of hero data that the !{_Angular_Http} client fetched from the server. - - *Observables* are a big topic, beyond the scope of this chapter. - 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 + + Think of an `Observable` as a stream of events published by some source. + To listen for events in this stream, ***subscribe*** to the `Observable`. + These subscriptions 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 - With our basic intuitions about the component squared away, we're ready to look inside the `HeroService`. + With a basic understanding of the component, you're ready to look inside the `HeroService`. a#HeroService .l-main-section#fetch-data :marked - ## Fetch data with the **HeroService** + ## Fetch data with http.get - In many of our previous samples we faked the interaction with the server by + In many of the previous samples the app faked the interaction with the server by returning mock heroes in a service like this one: +makeExample('toh-4/ts/app/hero.service.ts', 'just-get-heroes')(format=".") :marked - In this chapter, we revise that `HeroService` to get the heroes from the server using the !{_Angular_Http} client service: + You can revise that `HeroService` to get the heroes from the server using the !{_Angular_Http} client service: +makeExample('server-communication/ts/app/toh/hero.service.ts', 'v1', 'app/toh/hero.service.ts (revised)') :marked @@ -181,25 +178,25 @@ a#HeroService [injected](dependency-injection.html) into the `HeroService` constructor. +makeExample('server-communication/ts/app/toh/hero.service.ts', 'ctor') :marked - Look closely at how we call `!{_priv}http.get` + Look closely at how to call `!{_priv}http.get`: +makeExample('server-communication/ts/app/toh/hero.service.ts', 'http-get', 'app/toh/hero.service.ts (getHeroes)')(format=".") :marked - We pass the resource URL to `get` and it calls the server which should return heroes. + You pass the resource URL to `get` and it calls the server which returns heroes. + .l-sub-section :marked - It *will* return heroes once we've set up the [in-memory web api](#in-mem-web-api) + The server returns heroes once you've set up the [in-memory web api](#in-mem-web-api) described in the appendix below. - Alternatively, we can (temporarily) target a JSON file by changing the endpoint URL: + Alternatively, you can temporarily target a JSON file by changing the endpoint URL: +makeExample('server-communication/ts/app/toh/hero.service.ts', 'endpoint-json')(format=".") +ifDocsFor('ts') :marked - 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 + If you are familiar with asynchronous methods in modern JavaScript, you might expect the `get` method to return a [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. - Instead we're calling a `map()` method. + You'd expect to chain a call to `then()` and extract the heroes. + Instead you're calling a `map()` method. Clearly this is not a promise. In fact, the `http.get` method returns an **Observable** of HTTP Responses (`Observable`) from the RxJS library @@ -207,492 +204,497 @@ a#HeroService .l-main-section :marked - # RxJS Library + # RxJS library [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. - All of our Developer Guide samples have installed the RxJS npm package and loaded via `system.js` + All of the Developer Guide samples have installed the RxJS npm package and loaded via `system.js` because observables are used widely in Angular applications. - We certainly need it now when working with the HTTP client. - And we must take a critical extra step to make RxJS observables usable. - ### Enable RxJS Operators - The RxJS library is quite large. - Size matters when we build a production application and deploy it to mobile devices. - We should include only those features that we actually need. + The app needs it when working with the HTTP client. + Additionally, you must take a critical extra step to make RxJS observables usable. - Accordingly, Angular exposes a stripped down version of `Observable` in the `rxjs/Observable` module, - a version that lacks most of the operators including some we'd like to use here - such as the `map` method we called above in `getHeroes`. + ### Enable RxJS operators + The RxJS library is large. + Size matters when building a production application and deploying it to mobile devices. + You should include only necessary features. - It's up to us to add the operators we need. - - We could add _every_ RxJS operators with a single import statement. - 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. - - 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/rxjs-operators.ts` file. + Accordingly, Angular exposes a stripped down version of `Observable` in the `rxjs/Observable` + module that lacks most of the operators such as the `map` method you + called above in `getHeroes`. + + It's up to you to add the operators you need. + + You could add _every_ RxJS operator with a single import statement. + While that is the easiest thing to do, you'd pay a penalty in extended launch time + and application size because the full library is so big. + + Since this app only uses a few operators, it's better to import each `Observable` + operator and static class method, one-by-one, for a custom *Observable* + implementation tuned + precisely to the app's requirements. Put the `import` statements in one `app/rxjs-operators.ts` file. +makeExample('server-communication/ts/app/rxjs-operators.ts', null, 'app/rxjs-operators.ts')(format=".") :marked - If we forget an operator, the TypeScript compiler will warn that it's missing and we'll update this file. + If you forget an operator, the TypeScript compiler warns that it's missing and you'll update this file. .l-sub-section :marked - We don't need _all_ of these particular operators in the `HeroService` — just `map`, `catch` and `throw`. - We'll need the other operators later, in a *Wiki* example [below](#more-observables). + The app doesn't need _all_ of these particular operators in the `HeroService` — just `map`, `catch` and `throw`. + The other operators are for later, in the *Wiki* example [below](#more-observables). :marked - Finally, we import `rxjs-operator`_itself_ in our `app.component.ts`: + Finally, import `rxjs-operator` into `app.component.ts`: +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`. - + Now continue to the next section to return to the `HeroService`. + .l-main-section a#extract-data :marked ## 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 the `getHeroes()` method used an `!{_priv}extractData` helper method to map the `!{_priv}http.get` response object to heroes: +makeExample('server-communication/ts/app/toh/hero.service.ts', 'extract-data', 'app/toh/hero.service.ts (excerpt)')(format=".") :marked - The `response` object does not hold our data in a form we can use directly. - To make it useful in our application we must parse the response data into a JSON object + The `response` object doesn't hold the data in a form the app can use directly. + You must parse the response data into a JSON object. - #### Parse to JSON + ### Parse to JSON block parse-json :marked The response data are in JSON string form. - We must parse that string into JavaScript objects which we do by calling `response.json()`. + The app must parse that string into JavaScript objects by calling `response.json()`. + .l-sub-section :marked - This is not Angular's own design. + This is not Angular's own design. The Angular HTTP client follows the Fetch specification for the [response object](https://fetch.spec.whatwg.org/#response-class) returned by the `Fetch` function. That spec defines a `json()` method that parses the response body into a JavaScript object. .l-sub-section :marked - We shouldn't expect the decoded JSON to be the heroes !{_array} directly. - The server we're calling always wraps JSON results in an object with a `data` - property. We have to unwrap it to get the heroes. - This is conventional web api behavior, driven by + Don't expect the decoded JSON to be the heroes !{_array} directly. + This server always wraps JSON results in an object with a `data` + property. You have to unwrap it to get the heroes. + This is conventional web API behavior, driven by [security concerns](https://www.owasp.org/index.php/OWASP_AJAX_Security_Guidelines#Always_return_JSON_with_an_Object_on_the_outside). + .alert.is-important :marked - Make no assumptions about the server API. + Make no assumptions about the server API. Not all servers return an object with a `data` property. :marked ### Do not return the response object - Our `getHeroes()` could have returned the HTTP response. Bad idea! + The `getHeroes()` method _could_ have returned the HTTP response but this wouldn't + be a best practice. The point of a data service is to hide the server interaction details from consumers. - The component that calls the `HeroService` wants heroes. - It has no interest in what we do to get them. - It doesn't care where they come from. - And it certainly doesn't want to deal with a response object. + The component that calls the `HeroService` only wants heroes and is kept separate + from getting them, the code dealing with where they come from, and the response object. +ifDocsFor('ts') .callout.is-important - header HTTP GET is delayed + header HTTP GET is delayed :marked - The `!{_priv}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. + The `!{_priv}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 that the request won't go out until something *subscribes* to the observable. That *something* is the [HeroListComponent](#subscribe). a#error-handling :marked ### Always handle errors - Whenever we deal with I/O we must be prepared for something to go wrong as it surely will. - 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. - - In this simple app we provide rudimentary error handling in both the service and the component. -block error-handling - :marked - 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. - - We use the Observable `catch` operator on the service level. - It takes an error handling function with an error object as the argument. - 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`. + An important part of dealing with I/O is anticipating errors by preparing to catch them + and do something with them. One way to handle errors is to pass an error message + back to the component for presentation to the user, + but only if it says something that the user can understand and act upon. + + This simple app conveys that idea, albeit imperfectly, in the way it handles a `getHeroes` error. +makeExample('server-communication/ts/app/toh/hero.service.ts', 'error-handling', 'app/toh/hero.service.ts (excerpt)')(format=".") +block error-handling + :marked + The `catch` operator passes the error object from `http` to the `handleError` method. + The `handleError` method transforms the error into a developer-friendly message, + logs it to the console, and returns the message in a new, failed observable via `Observable.throw`. + a#subscribe a#hero-list-component -h4 #[b HeroListComponent] error handling +h3 #[b HeroListComponent] error handling block hlc-error-handling :marked - Back in the `HeroListComponent`, where we called `!{_priv}heroService.getHeroes()`, - 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 `HeroListComponent` template. + Back in the `HeroListComponent`, in `!{_priv}heroService.getHeroes()`, + the `subscribe` function has a second function parameter to handle the error message. + It sets an `errorMessage` variable that's 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=".") - + .l-sub-section :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? In the `HeroService`, reset the api endpoint to a bad value. Afterward, remember to restore it. + - .l-main-section :marked ## Send data to the server - - So far we've seen how to retrieve data from a remote location using an HTTP service. - Let's add the ability to create new heroes and save them in the backend. - We'll create an easy method for the `HeroListComponent` to call, an `addHero()` method that takes - just the name of a new hero: + So far you've seen how to retrieve data from a remote location using an HTTP service. + Now you'll add the ability to create new heroes and save them in the backend. + + You'll write a method for the `HeroListComponent` to call, an `addHero()` method, that takes + just the name of a new hero and returns an `Observable` of `Hero`. It begins like this: +makeExample('server-communication/ts/app/toh/hero.service.ts', 'addhero-sig')(format=".") :marked - To implement it, we need to know some details about the server's api for creating heroes. - - [Our data server](#server) follows typical REST guidelines. + To implement it, you must know the server's API for creating heroes. + + [This sample's data server](#server) follows typical REST guidelines. It expects a [`POST`](http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5) request - at the same endpoint where we `GET` heroes. - It expects the new hero data to arrive in the body of the request, + at the same endpoint as `GET` heroes. + It expects the new hero data to arrive in the body of the request, structured like a `Hero` entity but without the `id` property. The body of the request should look like this: - + code-example(format="." language="javascript"). { "name": "Windstorm" } :marked - The server will generate the `id` and return the entire `JSON` representation + The server generates the `id` and returns the entire `JSON` representation of the new hero including its generated id. The hero arrives tucked inside a response object with its own `data` property. - - Now that we know how the API works, we implement `addHero()`like this: + + Now that you know how the API works, implement `addHero()`as follows: +ifDocsFor('ts') - +makeExample('server-communication/ts/app/toh/hero.service.ts', 'import-request-options', 'app/toh/hero.service.ts (additional imports)')(format=".") + +makeExample('server-communication/ts/app/toh/hero.service.ts', 'import-request-options', 'app/toh/hero.service.ts (additional imports)')(format=".") +makeExample('server-communication/ts/app/toh/hero.service.ts', 'addhero', 'app/toh/hero.service.ts (addHero)')(format=".") :marked ### Headers - The `Content-Type` header allows us to inform the server that the body will represent JSON. + In the `headers` object, the `Content-Type` specifies that the body represents JSON. +ifDocsFor('ts') :marked - [Headers](../api/http/index/Headers-class.html) are one of the [RequestOptions](../api/http/index/RequestOptions-class.html). - Compose the options object and pass it in as the *third* parameter of the `post` method, as shown above. + Next, the `headers` object is used to configure the `options` object. The `options` + object is a new instance of `RequestOptions`, a class that allows you to specify + certain settings when instantiating a request. In this way, [headers](../api/http/index/Headers-class.html) is one of the [RequestOptions](../api/http/index/RequestOptions-class.html). -:marked - ### Body - - Despite the content type being specified as JSON, the POST body must actually be a *string*. - Hence, we explicitly encode the JSON hero content before passing it in as the body argument. - -+ifDocsFor('ts') - .l-sub-section - :marked - We may be able to skip the `JSON.stringify` step in the near future. + In the `return` statement, `options` is the *third* argument of the `post` method, as shown above. :marked ### JSON results - As with `getHeroes()`, we [extract the data](#extract-data) from the response using the - `!{_priv}extractData()` helper. + As with `getHeroes()`, use the `!{_priv}extractData()` helper to [extract the data](#extract-data) + from the response. block hero-list-comp-add-hero :marked - Back in the `HeroListComponent`, we see that *its* `addHero()` method subscribes to the observable returned by the *service's* `addHero()` method. - When the data arrives, it pushes the new hero object into its `heroes` array for presentation to the user. + Back in the `HeroListComponent`, *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. +makeExample('server-communication/ts/app/toh/hero-list.component.ts', 'addHero', 'app/toh/hero-list.component.ts (addHero)')(format=".") +ifDocsFor('ts') - h2#promises Fall back to Promises + h2#promises Fall back to promises :marked - 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. + Although the Angular `http` client API returns an `Observable` you can turn it into a + [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). + It's easy to do, and in simple cases, a promise-based version looks much + like the observable-based version. .l-sub-section :marked - While promises may be more familiar, observables have many advantages. - Don't rush to promises until you give observables a chance. + While promises may be more familiar, observables have many advantages. + :marked - Let's rewrite the `HeroService` using promises , highlighting just the parts that are different. + Here is a comparison of the `HeroService` using promises versus observables, + highlighting just the parts that are different. +makeTabs( - 'server-communication/ts/app/toh/hero.service.promise.ts,server-communication/ts/app/toh/hero.service.ts', - 'methods, methods', + 'server-communication/ts/app/toh/hero.service.promise.ts,server-communication/ts/app/toh/hero.service.ts', + 'methods, methods', 'app/toh/hero.service.promise.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)`. + You can follow the promise `then(this.extractData).catch(this.handleError)` pattern as in + this example. - 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. + Alternatively, you can call `toPromise(success, fail)`. The observable's `map` callback moves to the first *success* parameter and its `catch` callback to the second *fail* parameter in this pattern: `.toPromise(this.extractData, this.handleError)`. - Our `errorHandler` forwards an error message as a failed promise instead of a failed Observable. + The `errorHandler` forwards an error message as a failed promise instead of a failed `observable`. The diagnostic *log to console* is just one more `then` in the promise chain. - We have to adjust the calling component to expect a `Promise` instead of an `Observable`. + You have to adjust the calling component to expect a `Promise` instead of an `observable`: +makeTabs( - 'server-communication/ts/app/toh/hero-list.component.promise.ts, server-communication/ts/app/toh/hero-list.component.ts', - 'methods, methods', + 'server-communication/ts/app/toh/hero-list.component.promise.ts, server-communication/ts/app/toh/hero-list.component.ts', + 'methods, methods', 'app/toh/hero-list.component.promise.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. + The only obvious difference is that you call `then` on the returned promise instead of `subscribe`. + Both methods take the same functional arguments. .l-sub-section :marked - The less obvious but critical difference is that these two methods return very different results! + 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 promise-based `then` returns another promise. You 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 `subscribe` method returns a `Subscription`. A `Subscription` is not another `Observable`. + It's the end of the line for observables. You 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. + To understand the implications and consequences of subscriptions, watch [Ben Lesh's talk on observables](https://www.youtube.com/watch?v=3LKMwkuK0ZE) or his video course on [egghead.io](https://egghead.io/lessons/rxjs-rxjs-observables-vs-promises). h2#cors Cross-origin requests: Wikipedia example :marked - We just learned how to make `XMLHttpRequests` using the !{_Angular_Http} service. - This is the most common approach for server communication. - It doesn't work in all scenarios. + You just learned how to make `XMLHttpRequests` using the !{_Angular_Http} service. + This is the most common approach for server communication, but it doesn't work in all scenarios. For security reasons, web browsers block `XHR` calls to a remote server whose origin is different from the origin of the web page. - The *origin* is the combination of URI scheme, hostname and port number. - This is called the [Same-origin Policy](https://en.wikipedia.org/wiki/Same-origin_policy). + The *origin* is the combination of URI scheme, hostname, and port number. + This is called the [same-origin policy](https://en.wikipedia.org/wiki/Same-origin_policy). .l-sub-section :marked - Modern browsers do allow `XHR` requests to servers from a different origin if the server supports the + Modern browsers do allow `XHR` requests to servers from a different origin if the server supports the [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) protocol. - If the server requires user credentials, we'll enable them in the [request headers](#headers). + If the server requires user credentials, you'll enable them in the [request headers](#headers). :marked Some servers do not support CORS but do support an older, read-only alternative called [JSONP](https://en.wikipedia.org/wiki/JSONP). Wikipedia is one such server. .l-sub-section :marked - This [StackOverflow answer](http://stackoverflow.com/questions/2067472/what-is-jsonp-all-about/2067584#2067584) covers many details of JSONP. + This [Stack Overflow answer](http://stackoverflow.com/questions/2067472/what-is-jsonp-all-about/2067584#2067584) covers many details of JSONP. :marked ### Search wikipedia - - Let's build a simple search that shows suggestions from wikipedia as we type in a text box. + + Here is a simple search that shows suggestions from Wikipedia as the user + types in a text box: figure.image-display img(src='/resources/images/devguide/server-communication/wiki-1.gif' alt="Wikipedia search app (v.1)" width="250") block wikipedia-jsonp+ :marked - 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. - All other HTTP methods throw an error because JSONP is a read-only facility. + Wikipedia offers a modern `CORS` API and a legacy `JSONP` search API. This example uses the latter. + The Angular `Jsonp` service both extends the `!{_Http}` service for JSONP and restricts you to `GET` requests. + All other HTTP methods throw an error because JSONP is a read-only facility. - As always, we wrap our interaction with an Angular data access client service inside a dedicated service, here called `WikipediaService`. + As always, wrap the interaction with an Angular data access client service inside a dedicated service, here called `WikipediaService`. +makeExample('server-communication/ts/app/wiki/wikipedia.service.ts',null,'app/wiki/wikipedia.service.ts') :marked - The constructor expects Angular to inject its `jsonp` service. - We made that service available by importing the `JsonpModule` into our root NgModule. - + The constructor expects Angular to inject its `jsonp` service, which + is available because `JsonpModule` is in the root `@NgModule` `imports` array + in `app.module.ts`. + :marked ### Search parameters - The [Wikipedia 'opensearch' API](https://www.mediawiki.org/wiki/API:Opensearch) + The [Wikipedia "opensearch" API](https://www.mediawiki.org/wiki/API:Opensearch) expects four parameters (key/value pairs) to arrive in the request URL's query string. The keys are `search`, `action`, `format`, and `callback`. The value of the `search` key is the user-supplied search term to find in Wikipedia. The other three are the fixed values "opensearch", "json", and "JSONP_CALLBACK" respectively. .l-sub-section :marked - The `JSONP` technique requires that we pass a callback function name to the server in the query string: `callback=JSONP_CALLBACK`. - The server uses that name to build a JavaScript wrapper function in its response which Angular ultimately calls to extract the data. + The `JSONP` technique requires that you pass a callback function name to the server in the query string: `callback=JSONP_CALLBACK`. + The server uses that name to build a JavaScript wrapper function in its response, which Angular ultimately calls to extract the data. All of this happens under the hood. :marked - If we're looking for articles with the word "Angular", we could construct the query string by hand and call `jsonp` like this: + If you're looking for articles with the word "Angular", you could construct the query string by hand and call `jsonp` like this: +makeExample('server-communication/ts/app/wiki/wikipedia.service.1.ts','query-string')(format='.') :marked - In more parameterized examples we might prefer to build the query string with the Angular `URLSearchParams` helper as shown here: + In more parameterized examples you could build the query string with the Angular `URLSearchParams` helper: +makeExample('server-communication/ts/app/wiki/wikipedia.service.ts','search-parameters','app/wiki/wikipedia.service.ts (search parameters)')(format=".") :marked - This time we call `jsonp` with *two* arguments: the `wikiUrl` and an options object whose `search` property is the `params` object. + This time you call `jsonp` with *two* arguments: the `wikiUrl` and an options object whose `search` property is the `params` object. +makeExample('server-communication/ts/app/wiki/wikipedia.service.ts','call-jsonp','app/wiki/wikipedia.service.ts (call jsonp)')(format=".") :marked - `Jsonp` flattens the `params` object into the same query string we saw earlier before putting the request on the wire. + `Jsonp` flattens the `params` object into the same query string you saw earlier, putting the request on the wire. :marked ### The WikiComponent - Now that we have a service that can query the Wikipedia API, - we turn to the component that takes user input and displays search results. - + Now that you have a service that can query the Wikipedia API + turn to the component (template and class) that takes user input and displays search results. + +makeExample('server-communication/ts/app/wiki/wiki.component.html', null, 'app/wiki/wiki.component.html') +makeExample('server-communication/ts/app/wiki/wiki.component.ts', null, 'app/wiki/wiki.component.ts') :marked - The component presents an `` element *search box* to gather search terms from the user. + The template presents an `` element *search box* to gather search terms from the user, and calls a `search(term)` method after each `keyup` event. - - The `search(term)` method delegates to our `WikipediaService` which returns an observable array of string results (`Observable`). - Instead of subscribing to the observable inside the component as we did in the `HeroListComponent`, - we forward the observable result to the template (via `items`) where the [async pipe](pipes.html#async-pipe) - in the `ngFor` handles the subscription. + +makeExample('server-communication/ts/app/wiki/wiki.component.html', 'keyup', 'wiki/wiki.component.html')(format='.') + :marked + The component's `search(term)` method delegates to the `WikipediaService`, which returns an + observable array of string results (`Observable`). + Instead of subscribing to the observable inside the component, as in the `HeroListComponent`, + the app forwards the observable result to the template (via `items`) where the `async` pipe + in the `ngFor` handles the subscription. Read more about [async pipes](pipes.html#async-pipe) + in the [Pipes](pipes.html) page. .l-sub-section :marked - We often use the [async pipe](pipes.html#async-pipe) in read-only components where the component has no need to interact with the data. - We couldn't use the pipe in the `HeroListComponent` because the "add hero" feature pushes newly created heroes into the list. + The [async pipe](pipes.html#async-pipe) is a good choice in read-only components where the component has no need to interact with the data. + + `HeroListComponent` can't use the pipe because `addHero()` pushes newly created heroes into the list. :marked - ## Our wasteful app + ## A wasteful app - Our wikipedia search makes too many calls to the server. - It is inefficient and potentially expensive on mobile devices with limited data plans. + The wikipedia search makes too many calls to the server. + It is inefficient, and potentially expensive on mobile devices with limited data plans. ### 1. Wait for the user to stop typing - At the moment we call the server after every key stroke. - The app should only make requests when the user *stops typing* . - Here's how it *should* work — and *will* work — when we're done refactoring: + Presently, the code calls the server after every key stroke. + It should only make requests when the user *stops typing* . + Here's how it will work after refactoring: figure.image-display img(src='/resources/images/devguide/server-communication/wiki-2.gif' alt="Wikipedia search app (v.2)" width="250") :marked ### 2. Search when the search term changes - Suppose the user enters the word *angular* in the search box and pauses for a while. - The application issues a search request for *Angular*. + Suppose a user enters the word *angular* in the search box and pauses for a while. + The application issues a search request for *angular*. Then the user backspaces over the last three letters, *lar*, and immediately re-types *lar* before pausing once more. - The search term is still "angular". The app shouldn't make another request. + The search term is still _angular_. The app shouldn't make another request. ### 3. Cope with out-of-order responses - The user enters *angular*, pauses, clears the search box, and enters *http*. - The application issues two search requests, one for *angular* and one for *http*. + The user enters *angular*, pauses, clears the search box, and enters *http*. + The application issues two search requests, one for *angular* and one for *http*. - Which response will arrive first? We can't be sure. + Which response arrives first? It's unpredictable. A load balancer could dispatch the requests to two different servers with different response times. The results from the first *angular* request might arrive after the later *http* results. - The user will be confused if we display the *angular* results to the *http* query. + The user will be confused if the *angular* results display to the *http* query. When there are multiple requests in-flight, the app should present the responses in the original request order. That won't happen if *angular* results arrive last. - ## More fun with Observables - We can address these problems and improve our app with the help of some nifty observable operators. + ## More fun with observables + You can address these problems and improve the app with the help of some nifty observable operators. - We could make our changes to the `WikipediaService`. - But we sense that our concerns are driven by the user experience so we update the component class instead. + You could make changes to the `WikipediaService`, but for a better + user experience, create a copy of the `WikiComponent` instead and make it smarter. + Here's the `WikiSmartComponent` which uses the same template. +makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', null, 'app/wiki/wiki-smart.component.ts') :marked - We made no changes to the template or metadata, confining them all to the component class. - Let's review those changes. - ### Create a stream of search terms - We're binding to the search box `keyup` event and calling the component's `search` method after each keystroke. - - We turn these events into an observable stream of search terms using a `Subject` - which we import from the RxJS observable library: - +makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'import-subject') + The template still binds to the search box `keyup` event and passes the complete search box value + into the component's `search` method after every user keystroke. + +makeExample('server-communication/ts/app/wiki/wiki.component.html', 'keyup', 'app/wiki/wiki.component.html (input)')(format='.') :marked - Each search term is a string, so we create a new `Subject` of type `string` called `searchTermStream`. - After every keystroke, the `search` method adds the search box value to that stream - via the subject's `next` method. - +makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'subject')(format='.') + The `WikiSmartComponent` turns the search box values into an observable _stream of search terms_ + with the help of a `Subject` which you import from the RxJS observable library: + +makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'import-subject', 'app/wiki/wiki-smart.component.ts') + :marked + The component creates a `searchTermStream` as a `Subject` of type `string`. + The `search` method adds each new search box value to that stream via the subject's `next` method. + + +makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'subject', 'app/wiki/wiki-smart.component.ts')(format='.') + :marked ### Listen for search terms - Earlier, we passed each search term directly to the service and bound the template to the service results. - Now we listen to the *stream of terms*, manipulating the stream before it reaches the `WikipediaService`. - +makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'observable-operators')(format='.') + Earlier, you passed each search term directly to the service and bound the template to the service results. + + Now you listen to the *stream of search terms*, manipulating the stream before it reaches the `WikipediaService`. + +makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'observable-operators', + 'app/wiki/wiki-smart.component.ts')(format='.') :marked - We wait for the user to stop typing for at least 300 milliseconds - ([debounceTime](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/debounce.md)). - Only changed search values make it through to the service - ([distinctUntilChanged](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/distinctuntilchanged.md)). + Wait for the user to stop typing for at least 300 milliseconds + ([_debounceTime_](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/debounce.md)). + Only changed search values make it through to the service + ([_distinctUntilChanged_](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/distinctuntilchanged.md)). The `WikipediaService` returns a separate observable of string arrays (`Observable`) for each request. - We could have multiple requests *in flight*, all awaiting the server's reply, + There could be multiple requests *in-flight*, all awaiting the server's reply, which means multiple *observables-of-strings* could arrive at any moment in any order. - The [switchMap](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/flatmaplatest.md) - (formerly known as `flatMapLatest`) returns a new observable that combines these `WikipediaService` observables, + The [_switchMap_](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/flatmaplatest.md) + (formerly known as `flatMapLatest`) returns a new observable that combines these `WikipediaService` observables, re-arranges them in their original request order, - and delivers to subscribers only the most recent search results. + and delivers to subscribers only the most recent search results. The displayed list of search results stays in sync with the user's sequence of search terms. .l-sub-section :marked - We added the `debounceTime`, `distinctUntilChanged`, and `switchMap` operators to the RxJS `Observable` class - in `rxjs-operators` as [described above](#rxjs) + You added the `debounceTime`, `distinctUntilChanged`, and `switchMap` operators to the RxJS `Observable` class + in `rxjs-operators` as [described above](#rxjs). a#in-mem-web-api .l-main-section :marked ## Appendix: Tour of Heroes in-memory server - If we only cared to retrieve data, we could tell Angular to get the heroes from a `heroes.json` file like this one: + If the app only needed to retrieve data, you could get the heroes from a `heroes.json` file: +makeJson('server-communication/ts/app/heroes.json', null, 'app/heroes.json')(format=".") .l-sub-section :marked - We wrap the heroes array in an object with a `data` property for the same reason that a data server does: + You wrap the heroes array in an object with a `data` property for the same reason that a data server does: to mitigate the [security risk](http://stackoverflow.com/questions/3503102/what-are-top-level-json-arrays-and-why-are-they-a-security-risk) - posed by top-level JSON arrays. + posed by top-level JSON arrays. :marked - We'd set the endpoint to the JSON file like this: -+makeExample('server-communication/ts/app/toh/hero.service.ts', 'endpoint-json')(format=".") + You'd set the endpoint to the JSON file like this: ++makeExample('server-communication/ts/app/toh/hero.service.ts', 'endpoint-json', 'app/toh/hero.service.ts')(format=".") - var _a_ca_class_with = _docsFor === 'ts' ? 'a custom application class with' : '' :marked - The *get heroes* scenario would work. - But we want to *save* data too. We can't save changes to a JSON file. We need a web API server. - We didn't want the hassle of setting up and maintaining a real server for this chapter. - So we turned to an *in-memory web API simulator* instead. + The *get heroes* scenario would work, + but since the app can't save changes to a JSON file, it needs a web API server. + Because there isn't a real server for this demo, + it uses an *in-memory web API simulator* instead. .l-sub-section :marked - The in-memory web api is not part of the Angular core. + The in-memory web api is not part of the Angular core. It's an optional service in its own `angular-in-memory-web-api` library - that we installed with npm (see `package.json`) and - registered for module loading by SystemJS (see `systemjs.config.js`) + installed with npm (see `package.json`) and + registered for module loading by SystemJS (see `systemjs.config.js`). :marked The in-memory web API gets its data from !{_a_ca_class_with} a `createDb()` - 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. - - Here's the class we created for this sample based on the JSON data: + + Here's the class for this sample, based on the JSON data: +makeExample('server-communication/ts/app/hero-data.ts', null, 'app/hero-data.ts')(format=".") :marked Ensure that the `HeroService` endpoint refers to the web API: -+makeExample('server-communication/ts/app/toh/hero.service.ts', 'endpoint')(format=".") -:marked - Finally, redirect client HTTP requests to the in-memory web API. ++makeExample('server-communication/ts/app/toh/hero.service.ts', 'endpoint', 'app/toh/hero.service.ts')(format=".") + block redirect-to-web-api :marked - This redirection is easy to configure with the in-memory web API service module - by adding the `InMemoryWebApiModule` to the `AppModule.imports` list. - At the same time, we're calling its `forRoot` configuration method with the `HeroData` class. - +makeExample('server-communication/ts/app/app.module.ts', 'in-mem-web-api')(format=".") + Finally, redirect client HTTP requests to the in-memory web API by + adding the `InMemoryWebApiModule` to the `AppModule.imports` list. + At the same time, call its `forRoot` configuration method with the `HeroData` class. + +makeExample('server-communication/ts/app/app.module.ts', 'in-mem-web-api', 'app/app.module.ts')(format=".") :marked ### How it works Angular's `http` service delegates the client/server communication tasks - to a helper service called the `XHRBackend`. + to a helper service called the `XHRBackend`. - Using standard Angular provider registration techniques, the `InMemoryWebApiModule` - replaces the default `XHRBackend` service with its own in-memory alternative. - The `forRoot` method initialize the in-memory web API with *seed data* from the mock hero dataset at the same time. + Using standard Angular provider registration techniques, the `InMemoryWebApiModule` + replaces the default `XHRBackend` service with its own in-memory alternative. + At the same time, the `forRoot` method initializes the in-memory web API with the *seed data* from the mock hero dataset. .l-sub-section :marked - The `forRoot` method name is a strong reminder that you should only call the `InMemoryWebApiModule` _once_ - while setting the metadata for the root `AppModule`. Don't call it again!. + The `forRoot` method name is a strong reminder that you should only call the `InMemoryWebApiModule` _once_, + while setting the metadata for the root `AppModule`. Don't call it again. :marked - Here is the revised (and final) version of app/app.module.ts demonstrating these steps. + Here is the final, revised version of app/app.module.ts, demonstrating these steps. +makeExcerpt('app/app.module.ts') - +.alert.is-important + :marked + Import the `InMemoryWebApiModule` _after_ the `HttpModule` to ensure that + the `XHRBackend` provider of the `InMemoryWebApiModule` supersedes all others. :marked See the full source code in the .