In Angular 2, the Http
module now by default utilizes the Observable
pattern to wrap XMLHttpRequest
. For developers that are familiar with the pattern, it readily translates to the asynchronous nature of requests to remote resources. For developers that are newer to the pattern, learning the ins and outs of Http Observables
is a good way to wrap your head around this new paradigm.
The code, links, and a live example related to this are available at .
For the purpose of this example, you'll just serve a static JSON file to the application. However note that this would be no different if you were sending requests to a dynamic API endpoint.
Begin by creating a skeleton component, including all the necessary modules for making HTTP requests:
[app/article.component.ts] import {Component} from '@angular/core'; import {Http} from '@angular/http'; @Component({ selector: 'article', template: ` <h1>{{title}}</h1> <p>{{author}}</p> ` }) export class ArticleComponent { title:string; body:string; constructor (private http: Http) { } }
For this example, assume there is a JSON file inside the static directory named article.json
:
[article.json] { "title": "Orthopedic Doctors Ask City for More Sidewalk Cracks", "author": "Jake Hsu" }
Since you have already injected the Http
service, you can begin by defining the get request:
[app/article.component.ts] import {Component} from '@angular/core'; import {Http} from '@angular/http'; @Component({ selector: 'article', template: ` <h1>{{title}}</h1> <p>{{author}}</p> ` }) export class ArticleComponent { title:string; body:string; constructor (private http_: Http) { http_.get('static/article.json'); } }
This creates an Observable
instance, but you still need to add instructions on how to handle the raw string of the response.
At this point, you will notice that this does not actually fire a browser GET request. This is covered in this recipe's There's more section.
Since you know the request will return JSON, you can utilize the json()
method that a Response
would expose. This can be done inside the map()
method. However, the Observable
does not expose the map()
method by default, so you must import it from the rxjs
module:
[app/article.component.ts] import {Component} from '@angular/core'; import {Http} from '@angular/http'; import 'rxjs/add/operator/map'; @Component({ selector: 'article', template: ` <h1>{{title}}</h1> <p>{{author}}</p> ` }) export class ArticleComponent { title:string; author:string; constructor (private http_: Http) { http_.get('static/article.json') .map(response => response.json()); } }
So far so good, but you're still not done. The preceding code will create the Observable
instance, but you still have to subscribe to it in order to handle any data it would emit. This can be accomplished with the subscribe()
method, which allows you to attach the callback and error handling methods of observer
:
[app/article.component.ts] import {Component} from '@angular/core'; import {Http} from '@angular/http'; import 'rxjs/add/operator/map'; @Component({ selector: 'article', template: ` <h1>{{title}}</h1> <p>{{author}}</p> ` }) export class ArticleComponent { title:string; author:string; constructor (private http_: Http) { http_.get('static/article.json') .map(response => response.json()) .subscribe( article => { this.title = article.title; this.author = article.author; }, error => console.error(error)); } }
With all of this, the GET request will return the JSON file, and the response data will be parsed and its data interpolated into the DOM by the component.
The previous section gave a good high-level overview of what was happening, but it is useful to break things down more carefully to understand what each individual step accomplishes.
The Http
service class exposes the methods get()
, post()
, put()
, and so on—all the HTTP verbs that you would expect. Each of these will return Observable<Response>
, which will emit a Response
instance when the request is returned:
console.log(http_.get('static/article.json')); // Observable { ... }
It sounds obvious, but Observables
are observed by an observer
. The observer
will wait for Observable
to emit objects, which in this example takes the form of Response
.
The Response
instance exposes a json()
method, which converts the returned serialized payload string into its corresponding in-memory object representation. You would like to be able to pass a regular object to the observer
handler, so the ideal tool here is a wedge method that still gives you an Observable
in the end:
console.log(http_.get('static/article.json') .map(response => response.json())); // Observable {source: Observable, operator: MapOperator, ...}
Recall that the canonical form of Observables
is a stream of events. In this case, we know there will only ever be one event, which is the HTTP response. Nonetheless, all the normal operators that would be used on a stream of events can just as easily be used on this single-event Observable
.
In the same way that Array.map()
can be used to transform each instance in the array, Observable.map()
allows you to transform each event emitted from Observable
. More specifically, it creates another Observable
that emits the modified event passed from the initial observable.
Observable
instances expose a subscribe()
method that accepts an onNext
handler, an onError
handler, and an onCompleted
handler as arguments. These handlers correspond to the events in the life cycle of the Observable
when it emits Response
instances. The parameter for the onNext
method is whatever is emitted from the Observable
. In this case, the emitted data is the returned value from map()
, so it will be the parsed object that has returned after invoking json()
on the Response
instance.
All these methods are optional, but in this example, the onNext
and onError
methods are useful.
Together, these methods when provided to subscribe()
constitute what is identified as the observer
.
http_.get('static/article.json') .map(respose => respose.json()) .subscribe( article => { this.title = article.title; this.body = article.body; }, error => console.error(error));
With all of this together, the browser will fetch the JSON and parse it, and the subscriber will pass its data to the respective component members.
When constructing this recipe piece by piece, if you are watching your browser's network requests as you assemble it, you will notice that the actual GET request is not fired until the subscribe()
method is invoked. This is because the type Observable
you are using is "cold".
The "cold" designation means that the Observable
does not begin to emit until an observer
begins to subscribe to it. This is different from a "hot" Observable
, which will emit items even if there are no observers
subscribed to it. Since this means that events that occur before an observer
is attached are lost, HTTP Observables
demand a cold designation.
The onNext
method is termed "emission" since there is associated data that is being emitted. The onCompleted
and onError
methods are termed "notifications," as they represent something of significance, but they do not have an associated event that would be considered part of the stream.