Use an HTTP Client to talk to a remote server.
HTTP is the primary protocol for browser/server communication.
The
WebSocket
protocol is another important communication technology; it isn't covered in this page.
Modern browsers support two HTTP-based APIs: XMLHttpRequest (XHR) and JSONP. A few browsers also support Fetch.
The Angular HTTP library simplifies application programming with the XHR and JSONP APIs.
http.get()
A live example illustrates these topics.
This page describes server communication with the help of the following demos:
The root AppComponent
orchestrates these demos:
import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: ` <hero-list></hero-list> <hero-list-promise></hero-list-promise> <my-wiki></my-wiki> <my-wiki-smart></my-wiki-smart> ` }) export class AppComponent { }
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.
When importing from the
@angular/http
module, SystemJS knows how to load services from the Angular HTTP library because thesystemjs.config.js
file maps to that module name.
Before you can use the Http
client, you need to register it as a service provider with the dependency injection system.
Read about providers in the Dependency Injection page.
Register providers by importing other NgModules to the root NgModule in app.module.ts
.
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { HttpModule, JsonpModule } from '@angular/http'; import { AppComponent } from './app.component'; @NgModule({ imports: [ BrowserModule, FormsModule, HttpModule, JsonpModule ], declarations: [ AppComponent ], bootstrap: [ AppComponent ] }) export class AppModule { }
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 on the import
statement.
To add these modules to the application, pass them to the imports
array in the root @NgModule
.
The
HttpModule
is necessary for making HTTP calls. Though theJsonpModule
isn't necessary for plain HTTP, there is a JSONP demo later in this page. Loading its module now saves time.
The first demo is a mini-version of the 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:
This demo has a single component, the HeroListComponent
. Here's its template:
<h1>Tour of Heroes ({{mode}})</h1> <h3>Heroes:</h3> <ul> <li *ngFor="let hero of heroes">{{hero.name}}</li> </ul> <label>New hero name: <input #newHeroName /></label> <button (click)="addHero(newHeroName.value); newHeroName.value=''">Add Hero</button> <p class="error" *ngIf="errorMessage">{{errorMessage}}</p>
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, newHeroName
, accesses the value of the input box in the (click)
event binding. When the user clicks the button, that value is passed 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.
Here's the component class:
export class HeroListComponent implements OnInit { errorMessage: string; heroes: Hero[]; mode = 'Observable'; constructor (private heroService: HeroService) {} ngOnInit() { this.getHeroes(); } getHeroes() { this.heroService.getHeroes() .subscribe( heroes => this.heroes = heroes, error => this.errorMessage = <any>error); } addHero(name: string) { if (!name) { return; } this.heroService.create(name) .subscribe( hero => this.heroes.push(hero), error => this.errorMessage = <any>error); } }
Angular injects 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 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, you don't call the service's get
method in the component's constructor. Instead, call it inside the ngOnInit
lifecycle hook and rely on Angular to call ngOnInit
when it instantiates this component.
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.
The service's getHeroes()
and create()
methods return an Observable
of hero data that the Angular Http
client fetched from the server.
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).
With a basic understanding of the component, you're ready to look inside the HeroService
.
In many of the previous samples the app faked the interaction with the server by returning mock heroes in a service like this one:
import { Injectable } from '@angular/core'; import { Hero } from './hero'; import { HEROES } from './mock-heroes'; @Injectable() export class HeroService { getHeroes(): Promise<Hero[]> { return Promise.resolve(HEROES); } }
You can revise that HeroService
to get the heroes from the server using the Angular Http
client service:
import { Injectable } from '@angular/core'; import { Http, Response } from '@angular/http'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/map'; import { Hero } from './hero'; @Injectable() export class HeroService { private heroesUrl = 'api/heroes'; // URL to web API constructor (private http: Http) {} getHeroes(): Observable<Hero[]> { return this.http.get(this.heroesUrl) .map(this.extractData) .catch(this.handleError); } private extractData(res: Response) { let body = res.json(); return body.data || { }; } private handleError (error: Response | any) { // In a real world app, you might use a remote logging infrastructure 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); } }
Notice that the Angular Http
client service is injected into the HeroService
constructor.
constructor (private http: Http) {}
Look closely at how to call http.get
:
getHeroes(): Observable<Hero[]> { return this.http.get(this.heroesUrl) .map(this.extractData) .catch(this.handleError); }
You pass the resource URL to get
and it calls the server which returns heroes.
The server returns heroes once you've set up the in-memory web api described in the appendix below. Alternatively, you can temporarily target a JSON file by changing the endpoint URL:
private heroesUrl = 'app/heroes.json'; // URL to JSON file
If you are familiar with asynchronous methods in modern JavaScript, you might expect the get
method to return a promise. 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<Response>
) from the RxJS library and map()
is one of the RxJS operators.
RxJS is a third party library, endorsed by Angular, that implements the asynchronous Observable pattern.
All of the Developer Guide samples have installed the RxJS npm package because Observables are used widely in Angular applications. This app needs it when working with the HTTP client. But you must take a critical extra step to make RxJS Observables usable: you must import the RxJS operators individually.
The RxJS library is large. Size matters when building a production application and deploying it to mobile devices. You should include only necessary features.
Each code file should add the operators it needs by importing from an RxJS library. The getHeroes()
method needs the map()
and catch()
operators so it imports them like this.
import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/map';
Remember that the getHeroes()
method used an extractData()
helper method to map the http.get
response object to heroes:
private extractData(res: Response) { let body = res.json(); return body.data || { }; }
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.
The response data are in JSON string form. The app must parse that string into JavaScript objects by calling response.json()
.
This is not Angular's own design. The Angular HTTP client follows the Fetch specification for the response object returned by the
Fetch
function. That spec defines ajson()
method that parses the response body into a JavaScript object.
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.
Make no assumptions about the server API. Not all servers return an object with a
data
property.
The getHeroes()
method could have returned the HTTP response but this wouldn't follow best practices. The point of a data service is to hide the server interaction details from consumers. 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.
The http.get
does not send the request just yet. This Observable is cold, which means that the request won't go out until something subscribes to the Observable. That something is the HeroListComponent.
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.
getHeroes(): Observable<Hero[]> { return this.http.get(this.heroesUrl) .map(this.extractData) .catch(this.handleError); } private handleError (error: Response | any) { // In a real world app, you might use a remote logging infrastructure 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); }
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
.
Back in the HeroListComponent
, in 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.
getHeroes() { this.heroService.getHeroes() .subscribe( heroes => this.heroes = heroes, error => this.errorMessage = <any>error); }
Want to see it fail? In the
HeroService
, reset the api endpoint to a bad value. Afterward, remember to restore it.
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, a create()
method, that takes just the name of a new hero and returns an Observable
of Hero
. It begins like this:
create(name: string): Observable<Hero> {
To implement it, you must know the server's API for creating heroes.
This sample's data server follows typical REST guidelines. It expects a POST
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:
{ "name": "Windstorm" }
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 you know how the API works, implement create()
as follows:
import { Headers, RequestOptions } from '@angular/http';
create(name: string): Observable<Hero> { let headers = new Headers({ 'Content-Type': 'application/json' }); let options = new RequestOptions({ headers: headers }); return this.http.post(this.heroesUrl, { name }, options) .map(this.extractData) .catch(this.handleError); }
In the headers
object, the Content-Type
specifies that the body represents JSON.
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 is one of the RequestOptions.
In the return
statement, options
is the third argument of the post()
method, as shown above.
As with getHeroes()
, use the extractData()
helper to extract the data from the response.
Back in the HeroListComponent
, its addHero()
method subscribes to the Observable returned by the service's create()
method. When the data arrive it pushes the new hero object into its heroes
array for presentation to the user.
addHero(name: string) { if (!name) { return; } this.heroService.create(name) .subscribe( hero => this.heroes.push(hero), error => this.errorMessage = <any>error); }
Although the Angular http
client API returns an Observable<Response>
you can turn it into a Promise<Response>
. It's easy to do, and in simple cases, a Promise-based version looks much like the Observable-based version.
While Promises may be more familiar, Observables have many advantages.
Here is a comparison of the HeroService
using Promises versus Observables, highlighting just the parts that are different.
getHeroes (): Promise<Hero[]> { return this.http.get(this.heroesUrl) .toPromise() .then(this.extractData) .catch(this.handleError); } addHero (name: string): Promise<Hero> { let headers = new Headers({ 'Content-Type': 'application/json' }); let options = new RequestOptions({ headers: headers }); return this.http.post(this.heroesUrl, { name }, options) .toPromise() .then(this.extractData) .catch(this.handleError); } private extractData(res: Response) { let body = res.json(); return body.data || { }; } private handleError (error: Response | any) { // In a real world app, we might use a remote logging infrastructure 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); }
getHeroes(): Observable<Hero[]> { return this.http.get(this.heroesUrl) .map(this.extractData) .catch(this.handleError); } create(name: string): Observable<Hero> { let headers = new Headers({ 'Content-Type': 'application/json' }); let options = new RequestOptions({ headers: headers }); return this.http.post(this.heroesUrl, { name }, options) .map(this.extractData) .catch(this.handleError); } private extractData(res: Response) { let body = res.json(); return body.data || { }; } private handleError (error: Response | any) { // In a real world app, you might use a remote logging infrastructure 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); }
You can follow the Promise then(this.extractData).catch(this.handleError)
pattern as in this 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)
.
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.
You have to adjust the calling component to expect a Promise
instead of an Observable
:
getHeroes() { this.heroService.getHeroes() .then( heroes => this.heroes = heroes, error => this.errorMessage = <any>error); } addHero (name: string) { if (!name) { return; } this.heroService.addHero(name) .then( hero => this.heroes.push(hero), error => this.errorMessage = <any>error); }
getHeroes() { this.heroService.getHeroes() .subscribe( heroes => this.heroes = heroes, error => this.errorMessage = <any>error); } addHero(name: string) { if (!name) { return; } this.heroService.create(name) .subscribe( hero => this.heroes.push(hero), error => this.errorMessage = <any>error); }
The only obvious difference is that you call then()
on the returned Promise instead of subscribe
. Both methods take the same functional arguments.
The less obvious but critical difference is that these two methods return very different results.
The Promise-based
then()
returns another Promise. You can keep chaining morethen()
andcatch()
calls, getting a new promise each time.The
subscribe()
method returns aSubscription
. ASubscription
is not anotherObservable
. It's the end of the line for Observables. You can't callmap()
on it or callsubscribe()
again. TheSubscription
object has a different purpose, signified by its primary method,unsubscribe
.To understand the implications and consequences of subscriptions, watch Ben Lesh's talk on Observables or his video course on egghead.io.
You just learned how to make XMLHttpRequests
using the Angular Http
service. This is the most common approach to 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.
Modern browsers do allow
XHR
requests to servers from a different origin if the server supports the CORS protocol. If the server requires user credentials, enable them in the request headers.
Some servers do not support CORS but do support an older, read-only alternative called JSONP. Wikipedia is one such server.
This Stack Overflow answer covers many details of JSONP.
Here is a simple search that shows suggestions from Wikipedia as the user types in a text box:
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, wrap the interaction with an Angular data access client service inside a dedicated service, here called WikipediaService
.
import { Injectable } from '@angular/core'; import { Jsonp, URLSearchParams } from '@angular/http'; import 'rxjs/add/operator/map'; @Injectable() export class WikipediaService { constructor(private jsonp: Jsonp) {} search (term: string) { let wikiUrl = 'http://en.wikipedia.org/w/api.php'; let params = new URLSearchParams(); params.set('search', term); // the user's search value params.set('action', 'opensearch'); params.set('format', 'json'); params.set('callback', 'JSONP_CALLBACK'); // TODO: Add error handling return this.jsonp .get(wikiUrl, { search: params }) .map(response => <string[]> response.json()[1]); } }
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
.
The Wikipedia "opensearch" API 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.
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.
If you're looking for articles with the word "Angular", you could construct the query string by hand and call jsonp
like this:
let queryString = `?search=${term}&action=opensearch&format=json&callback=JSONP_CALLBACK`; return this.jsonp .get(wikiUrl + queryString) .map(response => <string[]> response.json()[1]);
In more parameterized examples you could build the query string with the Angular URLSearchParams
helper:
let params = new URLSearchParams(); params.set('search', term); // the user's search value params.set('action', 'opensearch'); params.set('format', 'json'); params.set('callback', 'JSONP_CALLBACK');
This time you call jsonp
with two arguments: the wikiUrl
and an options object whose search
property is the params
object.
// TODO: Add error handling return this.jsonp .get(wikiUrl, { search: params }) .map(response => <string[]> response.json()[1]);
Jsonp
flattens the params
object into the same query string you saw earlier, sending the request to the server.
Now that you have a service that can query the Wikipedia API, turn your attention to the component (template and class) that takes user input and displays search results.
import { Component } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { WikipediaService } from './wikipedia.service'; @Component({ selector: 'my-wiki', template: ` <h1>Wikipedia Demo</h1> <p>Search after each keystroke</p> <input #term (keyup)="search(term.value)"/> <ul> <li *ngFor="let item of items | async">{{item}}</li> </ul>`, providers: [ WikipediaService ] }) export class WikiComponent { items: Observable<string[]>; constructor (private wikipediaService: WikipediaService) { } search (term: string) { this.items = this.wikipediaService.search(term); } }
The template presents an <input>
element search box to gather search terms from the user, and calls a search(term)
method after each keyup
event.
The component's search(term)
method delegates to the WikipediaService
, which returns an Observable array of string results (Observable<string[]>
). 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 in the Pipes page.
The 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 becauseaddHero()
pushes newly created heroes into the list.
The Wikipedia search makes too many calls to the server. It is inefficient and potentially expensive on mobile devices with limited data plans.
Presently, the code calls the server after every keystroke. It should only make requests when the user stops typing. Here's how it will work after refactoring:
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 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 arrives first? It's unpredictable. When there are multiple requests in-flight, the app should present the responses in the original request order. In this example, the app must always display the results for the http search no matter which response arrives first.
You could make changes to the WikipediaService
, but for a better user experience, create a copy of the WikiComponent
instead and make it smarter, with the help of some nifty Observable operators.
Here's the WikiSmartComponent
, shown next to the original WikiComponent
:
import { Component, OnInit } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/debounceTime'; import 'rxjs/add/operator/distinctUntilChanged'; import 'rxjs/add/operator/switchMap'; import { Subject } from 'rxjs/Subject'; import { WikipediaService } from './wikipedia.service'; @Component({ selector: 'my-wiki-smart', template: ` <h1>Smarter Wikipedia Demo</h1> <p>Search when typing stops</p> <input #term (keyup)="search(term.value)"/> <ul> <li *ngFor="let item of items | async">{{item}}</li> </ul>`, providers: [ WikipediaService ] }) export class WikiSmartComponent implements OnInit { items: Observable<string[]>; constructor (private wikipediaService: WikipediaService) {} private searchTermStream = new Subject<string>(); search(term: string) { this.searchTermStream.next(term); } ngOnInit() { this.items = this.searchTermStream .debounceTime(300) .distinctUntilChanged() .switchMap((term: string) => this.wikipediaService.search(term)); } }
import { Component } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { WikipediaService } from './wikipedia.service'; @Component({ selector: 'my-wiki', template: ` <h1>Wikipedia Demo</h1> <p>Search after each keystroke</p> <input #term (keyup)="search(term.value)"/> <ul> <li *ngFor="let item of items | async">{{item}}</li> </ul>`, providers: [ WikipediaService ] }) export class WikiComponent { items: Observable<string[]>; constructor (private wikipediaService: WikipediaService) { } search (term: string) { this.items = this.wikipediaService.search(term); } }
While the templates are virtually identical, there's a lot more RxJS in the "smart" version, starting with debounceTime
, distinctUntilChanged
, and switchMap
operators, imported as described above.
The WikiComponent
passes a new search term directly to the WikipediaService
after every keystroke.
The WikiSmartComponent
class turns the user's keystrokes into an Observable stream of search terms with the help of a Subject
, which you import from RxJS:
import { Subject } from 'rxjs/Subject';
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.
private searchTermStream = new Subject<string>(); search(term: string) { this.searchTermStream.next(term); }
The WikiSmartComponent
listens to the stream of search terms and processes that stream before calling the service.
this.items = this.searchTermStream .debounceTime(300) .distinctUntilChanged() .switchMap((term: string) => this.wikipediaService.search(term));
debounceTime waits for the user to stop typing for at least 300 milliseconds.
distinctUntilChanged ensures that the service is called only when the new search term is different from the previous search term.
The switchMap calls the WikipediaService
with a fresh, debounced search term and coordinates the stream(s) of service response.
The role of switchMap
is particularly important. The WikipediaService
returns a separate Observable of string arrays (Observable<string[]>
) for each search request. The user could issue multiple requests before a slow server has had time to reply, which means a backlog of response Observables could arrive at the client, at any moment, in any order.
The switchMap
returns its own Observable that combines all WikipediaService
response Observables, re-arranges them in their original request order, and delivers to subscribers only the most recent search results.
In a cross-site request forgery (CSRF or XSRF), an attacker tricks the user into visiting a different web page with malignant code that secretly sends a malicious request to your application's web server.
The server and client application must work together to thwart this attack. Angular's Http
client does its part by applying a default CookieXSRFStrategy
automatically to all requests.
The CookieXSRFStrategy
supports a common anti-XSRF technique in which the server sends a randomly generated authentication token in a cookie named XSRF-TOKEN
. The HTTP client adds an X-XSRF-TOKEN
header with that token value to subsequent requests. The server receives both the cookie and the header, compares them, and processes the request only if the cookie and header match.
See the XSRF topic on the Security page for more information about XSRF and Angular's XSRFStrategy
counter measures.
Request options (such as headers) are merged into the default RequestOptions before the request is processed. The HttpModule
provides these default options via the RequestOptions
token.
You can override these defaults to suit your application needs by creating a custom sub-class of RequestOptions
that sets the default options for the application.
This sample creates a class that sets the default Content-Type
header to JSON. It exports a constant with the necessary RequestOptions
provider to simplify registration in AppModule
.
import { Injectable } from '@angular/core'; import { BaseRequestOptions, RequestOptions } from '@angular/http'; @Injectable() export class DefaultRequestOptions extends BaseRequestOptions { constructor() { super(); // Set the default 'Content-Type' header this.headers.set('Content-Type', 'application/json'); } } export const requestOptionsProvider = { provide: RequestOptions, useClass: DefaultRequestOptions };
Then it registers the provider in the root AppModule
.
providers: [ requestOptionsProvider ],
Remember to include this provider during setup when unit testing the app's HTTP services.
After this change, the header
option setting in HeroService.create()
is no longer necessary,
create(name: string): Observable<Hero> { let headers = new Headers({ 'Content-Type': 'application/json' }); let options = new RequestOptions({ headers: headers }); return this.http.post(this.heroesUrl, { name }, options) .map(this.extractData) .catch(this.handleError); }
You can confirm that DefaultRequestOptions
is working by examing HTTP requests in the browser developer tools' network tab. If you're short-circuiting the server call with something like the in-memory web api, try commenting-out the create
header option, set a breakpoint on the POST call, and step through the request processing to verify the header is there.
Individual requests options, like this one, take precedence over the default RequestOptions
. It might be wise to keep the create
request header setting for extra safety.
If the app only needed to retrieve data, you could get the heroes from a heroes.json
file:
{ "data": [ { "id": 1, "name": "Windstorm" }, { "id": 2, "name": "Bombasto" }, { "id": 3, "name": "Magneta" }, { "id": 4, "name": "Tornado" } ] }
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 posed by top-level JSON arrays.
You'd set the endpoint to the JSON file like this:
private heroesUrl = 'app/heroes.json'; // URL to JSON file
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 substitutes the Angular in-memory web api simulator for the actual XHR backend service.
The in-memory web api is not part of Angular proper. It's an optional service in its own angular-in-memory-web-api library installed with npm (see
package.json
).See the README file for configuration options, default behaviors, and limitations.
The in-memory web API gets its data from a custom application class with a createDb()
method that returns a map whose keys are collection names and whose values are arrays of objects in those collections.
Here's the class for this sample, based on the JSON data:
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' } ]; return {heroes}; } }
Ensure that the HeroService
endpoint refers to the web API:
private heroesUrl = 'api/heroes'; // URL to web API
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.
InMemoryWebApiModule.forRoot(HeroData)
Angular's http
service delegates the client/server communication tasks 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. At the same time, the forRoot
method initializes the in-memory web API with the seed data from the mock hero dataset.
The
forRoot()
method name is a strong reminder that you should only call theInMemoryWebApiModule
once, while setting the metadata for the rootAppModule
. Don't call it again.
Here is the final, revised version of src/app/app.module.ts, demonstrating these steps.
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { HttpModule, JsonpModule } from '@angular/http'; import { InMemoryWebApiModule } from 'angular-in-memory-web-api'; import { HeroData } from './hero-data'; import { requestOptionsProvider } from './default-request-options.service'; import { AppComponent } from './app.component'; import { HeroListComponent } from './toh/hero-list.component'; import { HeroListPromiseComponent } from './toh/hero-list.component.promise'; import { WikiComponent } from './wiki/wiki.component'; import { WikiSmartComponent } from './wiki/wiki-smart.component'; @NgModule({ imports: [ BrowserModule, FormsModule, HttpModule, JsonpModule, InMemoryWebApiModule.forRoot(HeroData) ], declarations: [ AppComponent, HeroListComponent, HeroListPromiseComponent, WikiComponent, WikiSmartComponent ], providers: [ requestOptionsProvider ], bootstrap: [ AppComponent ] }) export class AppModule {}
Import the
InMemoryWebApiModule
after theHttpModule
to ensure that theXHRBackend
provider of theInMemoryWebApiModule
supersedes all others.
See the full source code in the live example.
© 2010–2017 Google, Inc.
Licensed under the Creative Commons Attribution License 4.0.
https://v2.angular.io/docs/ts/latest/guide/server-communication.html