Getting Started With Angular 2 Step by Step: 6 - Consuming Real Data with Http
UPDATE (22th May 2017): This tutorial has been updated to the latest version of Angular (Angular 4). Note that the Angular team has re-branded the terms Angular 2
to Angular
and Angular 1.x
to AngularJS
which are now the official names of these frameworks.
This is the sixth article on the Getting Started with Angular 2 Step by Step series if you missed the previous articles go and check them out!
Good day! Today we are going to wrap up this introductory series to Angular 2 with a look at how to retrieve real data from a web service using the http module in Angular 2. We’ll also learn a little bit about Observables and Rxjs (the new async weapon of choice in Angular 2) along the way.
Let’s get started!
The Code Samples
You can download all the code samples for this article from this GitHub repository.
Our Application Up Until Now
At this point in time we have developed a tiny Angular 2 application to learn more about people from the StarWars universe. We have two components, the PeopleListComponent
that displays a list of people and the PersonDetailsComponent
that displays more information about the character that you have selected. The person details are displayed within a form and you’re free to edit and save them.
We use Angular 2 routing to navigate between these two views, the list of people being our default view and the one that is rendered as soon as the application starts.
Let’s Get Some Real Data For Our App!
In the latest version of our sample application we were retrieving our data using a PeopleService
that just accessed a simple array of Person
:s stored in memory. It looked like this:
import { Injectable } from '@angular/core'
import { Person } from './person'
const PEOPLE: Person[] = [
{ id: 1, name: 'Luke Skywalker', height: 177, weight: 70, profession: '' },
{ id: 2, name: 'Darth Vader', height: 200, weight: 100, profession: '' },
{ id: 3, name: 'Han Solo', height: 185, weight: 85, profession: '' },
]
@Injectable()
export class PeopleService {
getAll(): Person[] {
return PEOPLE.map(p => this.clone(p))
}
get(id: number): Person {
return this.clone(PEOPLE.find(p => p.id === id))
}
save(person: Person) {
let originalPerson = PEOPLE.find(p => p.id === person.id)
if (originalPerson) Object.assign(originalPerson, person)
// saved muahahaha
}
private clone(object: any) {
// hack
return JSON.parse(JSON.stringify(object))
}
}
Let’s switch that for a more realistic scenario where we retrieve our data from an actual web service. For that purpose we will use the Star Wars API and Angular 2 Http module. But first things first, we need to enable it.
Enabling The Http Module In Your App
Te Angular 2 http module lives in a separate javascript file from the core of Angular 2 and it relies on the RxJS library as we’ll see in a bit. This means that potentially you could use any other http library to consume services over HTTP.
Because we used the Angular cli to boostrap our app the http module and rxjs
have been automatically added to our project and are ready to be used.
Indeed, if you take a sneak peek at the app’s package.json
you’ll see the following:
"dependencies": {
"@angular/common": "^4.0.0",
"@angular/compiler": "^4.0.0",
"@angular/core": "^4.0.0",
"@angular/forms": "^4.0.0",
"@angular/http": "^4.0.0", // <==== LOOK HERE!
"@angular/platform-browser": "^4.0.0",
"@angular/platform-browser-dynamic": "^4.0.0",
"@angular/router": "^4.0.0",
"core-js": "^2.4.1",
"rxjs": "^5.1.0", // <==== AND HERE!
"zone.js": "^0.8.4"
},
Yes! @angular/http
and rxjs
are already dependencies of our application.
Next take a look at your app’s main module AppModule
in app.module.ts
:
import { BrowserModule } from '@angular/platform-browser'
import { NgModule } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { HttpModule } from '@angular/http'
// other imports...
@NgModule({
declarations: [
// ...
],
imports: [
BrowserModule,
FormsModule,
HttpModule, // <=== HERE!
AppRoutingModule,
],
providers: [PeopleService],
bootstrap: [AppComponent],
})
export class AppModule {}
The HttpModule
is imported and setup as one of the imports
in our app’s main module. What does this mean? It means that we can start using all the services and features provided by Angular 2 http module from the get-go. Well played Angular cli… Well played.
Let’s Get Started Using The Http Module To Get Stuff From The Interwebs!
The Angular 2 http module @angular/http
exposes a Http
service that our application can use to access web services over HTTP. We’ll use this marvellous utility in our PeopleService
service. We start by importing it together will all types involved in doing an http request:
import { Http, Response } from '@angular/http'
import { Observable } from 'rxjs/Observable'
import 'rxjs/add/operator/map'
What? What was all that stuff that we imported in addition to Http
? These are all types and stuff required to make and handle an HTTP request to a web service with Angular:
Http
is an Angular 2 service that provides the API to make HTTP requests with methods corresponding to HTTP verbs likeget
,post
,put
, etcResponse
is a type that represents a response from an HTTP service and follows the fetch API specificationObservable
is the async pattern used in Angular 2. The concept of observable comes from the observer design pattern as an object that notifies an interested party of observers when something interesting happens. In RxJS it has been generalized to manage sequences of data or events (also know as streams), to become composable with other observables and to provide a lot of utility functions known as operators that let you achieve amazing stuff.rxjs/add/operator/map
refers torxjs
map
operator.rxjs
is a very big library with lots of operators and features. A best practice is to only import the parts ofrxjs
that you need to use and that’s why we start by only importing themap
operator (one of the most commonly used operators).
I know, I know, lot’s of new concepts, but don’t give up just yet, bear with me, everything will be explained soon!
After importing the necessary items we can inject the Http
service inside PeopleService
through its constructor:
@Injectable()
export class PeopleService {
constructor(private http: Http) {}
// other methods...
}
So! Now that we have injected the Http
service in our PeopleService
we can use it to get those pesky Star Wars personages. We will update our getAll
method like this:
Injectable()
export class PeopleService {
private baseUrl: string = 'http://swapi.co/api'
constructor(private http: Http) {}
getAll(): Observable<Person[]> {
let people$ = this.http
.get(`${this.baseUrl}/people`, { headers: this.getHeaders() })
.map(mapPersons)
return people$
}
private getHeaders() {
// I included these headers because otherwise FireFox
// will request text/html instead of application/json
let headers = new Headers()
headers.append('Accept', 'application/json')
return headers
}
// other code...
}
If you’ve worked with AngularJS http
service in the past, or if you are accustomed to using promises then this piece of code may look weird like hell. What is map
doing there? Where’s my then
? Who moved my cheese?
Well, the http.get
method makes a GET
request to the Star Wars API and returns an Observable<Response>
(not a Promise
like you probably where expecting). The Observable<Response>
can be described as a sequence of responses over time, although in this particular case, it is going to be a sequence of a single response.
Seeing it as a sequence or collection of responses over time it makes more sense to use a method like map
to transform the items within that sequence (just like we would do with Array.prototype.map
) into something more akin to the domain model of our application, for instance, persons.
If we think of it like that, we can define that mapPersons
function to transform a Response
into an array of persons as follows:
function mapPersons(response: Response): Person[] {
// The response of the API has a results
// property with the actual results
return response.json().results.map(toPerson)
}
function toPerson(r: any): Person {
let person = <Person>{
id: extractId(r),
url: r.url,
name: r.name,
weight: Number.parseInt(r.mass),
height: Number.parseInt(r.height),
}
console.log('Parsed person:', person)
return person
}
// to avoid breaking the rest of our app
// I extract the id from the person url
// that's because the Starwars API doesn't have an id field
function extractId(personData: any) {
let extractedId = personData.url
.replace('http://swapi.co/api/people/', '')
.replace('/', '')
return parseInt(extractedId)
}
Since now we are returning an Observable<Person[]>
from our getAll
method, we have broken the sacred bow we had made to the rest of our application that was specified by our interface and therefore we need to update the PeopleListComponent
. How can you unpack that array of persons from the Observable<Person[]>
? You subscribe
to the observable like this:
@Component({...})
export class PeopleListComponent implements OnInit {
people: Person[] = [];
constructor(private peopleService: PeopleService) { }
ngOnInit() {
this.peopleService
.getAll()
.subscribe(p => this.people = p);
}
}
This feels a lot like a then
method in a promise, doesn’t it?
We can continue consuming the Star Wars web service by following the same process with the get
method:
Injectable()
export class PeopleService {
// code ...
get(id: number): Observable<Person> {
let person$ = this.http
.get(`${this.baseUrl}/people/${id}`, { headers: this.getHeaders() })
.map(mapPerson)
return person$
}
}
// code...
function mapPerson(response: Response): Person {
// toPerson looks just like in the previous example
return toPerson(response.json())
}
And the save
method:
Injectable()
export class PeopleService {
// code ...
save(person: Person): Observable<Response> {
// this won't actually work because the StarWars API doesn't
// is read-only. But it would look like this:
return this.http.put(
`${this.baseUrl}/people/${person.id}`,
JSON.stringify(person),
{ headers: this.getHeaders() }
)
}
}
Since we have changed the public API of the PeopleService
service again, we’ll need to update the PersonDetailsComponent
that uses it. Once again, we subscribe
to the observables being returned:
@Component({...})
export class PersonDetailsComponent implements OnInit, OnDestroy {
person: Person;
sub: any;
professions: string[] = ['jedi', 'bounty hunter', 'princess', 'sith lord'];
constructor(private peopleService: PeopleService,
private route: ActivatedRoute,
private router: Router){
}
ngOnInit(){
this.sub = this.route.params.subscribe(params => {
let id = Number.parseInt(params['id']);
console.log('getting person with id: ', id);
this.peopleService
.get(id)
.subscribe(p => this.person = p);
});
}
ngOnDestroy(){
this.sub.unsubscribe();
}
gotoPeoplesList(){
let link = ['/persons'];
this.router.navigate(link);
}
savePersonDetails(){
this.peopleService
.save(this.person)
.subscribe(r => console.log(`saved!!! ${JSON.stringify(this.person)}`));
}
}
So now, when you run the application (remember ng serve --open
or ng s -o
) you’ll be able to see how it gets populated with new Star Wars Characters like you’ve never experienced before.
Here is the complete people.service.ts
for easier reference:
import { Injectable } from '@angular/core'
import { Http, Response, RequestOptions, Headers } from '@angular/http'
import { Observable } from 'rxjs/Observable'
import 'rxjs/add/operator/map'
import { Person } from './person'
const PEOPLE: Person[] = [
{ id: 1, name: 'Luke Skywalker', height: 177, weight: 70 },
{ id: 2, name: 'Darth Vader', height: 200, weight: 100 },
{ id: 3, name: 'Han Solo', height: 185, weight: 85 },
]
@Injectable()
export class PeopleService {
private baseUrl: string = 'http://swapi.co/api'
constructor(private http: Http) {}
getAll(): Observable<Person[]> {
let people$ = this.http
.get(`${this.baseUrl}/people`, { headers: this.getHeaders() })
.map(mapPersons)
return people$
}
private getHeaders() {
// I included these headers because otherwise FireFox
// will request text/html
let headers = new Headers()
headers.append('Accept', 'application/json')
return headers
}
get(id: number): Observable<Person> {
let person$ = this.http
.get(`${this.baseUrl}/people/${id}`, { headers: this.getHeaders() })
.map(mapPerson)
return person$
}
save(person: Person): Observable<Response> {
// this won't actually work because the StarWars API
// is read-only. But it would look like this:
return this.http.put(
`${this.baseUrl}/people/${person.id}`,
JSON.stringify(person),
{ headers: this.getHeaders() }
)
}
}
function mapPersons(response: Response): Person[] {
// The response of the API has a results
// property with the actual results
return response.json().results.map(toPerson)
}
function toPerson(r: any): Person {
let person = <Person>{
id: extractId(r),
url: r.url,
name: r.name,
weight: Number.parseInt(r.mass),
height: Number.parseInt(r.height),
}
console.log('Parsed person:', person)
return person
}
// to avoid breaking the rest of our app
// I extract the id from the person url
function extractId(personData: any) {
let extractedId = personData.url
.replace('http://swapi.co/api/people/', '')
.replace('/', '')
return parseInt(extractedId)
}
function mapPerson(response: Response): Person {
// toPerson looks just like in the previous example
return toPerson(response.json())
}
Finally! We have our whole application consuming a real web service and getting real data to display. Yey!
But… What happens when there’s an error response?
Error Handling with Observables
Observables, just like Promises, offer a straightforward way to handle errors. Since the PeopleService
and the PersonDetailsComponent
and PeopleListComponent
work at two different levels of abstraction (one deals with HTTP requests and responses and the other one with domain model objects), we are going to have two levels of error handling.
The first level of error handling happens at the service level and deals with problems that can happen with HTTP requests. In this simple application it will log the error and transform it into an application level error. We start by importing the catch
operator and the throw
method from rxjs
as follows:
import 'rxjs/add/operator/catch'
import 'rxjs/add/observable/throw'
And now we use these in our rxjs
streams. Specifically:
- We use
catch
to handle errors within a stream - We use
throw
to throw a new error at a higher level of abstraction
See below how catch
allows us to intercept errors within a stream:
Injectable()
export class PeopleService {
// code ...
getAll(): Observable<Person[]> {
let people$ = this.http
.get(`${this.baseUrl}/people`, { headers: this.getHeaders() })
.map(mapPersons)
.catch(handleError) // HERE: This is new!
return people$
}
// more code...
get(id: number): Observable<Person> {
let person$ = this._http
.get(`${this.baseUrl}/people/${id}`, { headers: this.getHeaders() })
.map(mapPerson)
.catch(handleError) // HERE: This is new!
return person$
}
}
And throw
let’s us push a new error upwards into our application:
// this could also be a private method of the component class
function handleError(error: any) {
// log error
// could be something more sofisticated
let errorMsg =
error.message ||
`Yikes! There was a problem with our hyperdrive device and we couldn't retrieve your data!`
console.error(errorMsg)
// throw an application level error
return Observable.throw(errorMsg)
}
The second level error handling happens at the application level inside the component:
@Component({
selector: 'people-list',
template: `
<ul>
<!-- this is the new syntax for ng-repeat -->
<li *ngFor="let person of people">
<a href="#" [routerLink]="['/persons', person.id]">
{{person.name}}
</a>
</li>
</ul>
<!-- HERE: added this error message -->
<section *ngIf="errorMessage">
{{errorMessage}}
</section>
`,
})
export class PeopleListComponent implements OnInit {
people: Person[] = []
errorMessage: string = ''
constructor(private peopleService: PeopleService) {}
ngOnInit() {
this.peopleService
.getAll()
.subscribe(
/* happy path */ p => (this.people = p),
/* error path */ e => (this.errorMessage = e)
)
}
}
Where we use the second argument of the subscribe
method to subscribe to errors and display error messages to the user.
Additionally, the subscribe
method lets you define a third argument with an onComplete
function to execute when a request is gracefully completed. We can use it to, for instance, toggle a progress spinner or a loading message:
import { Component, OnInit } from '@angular/core'
import { Person } from './person'
import { PeopleService } from './people.service'
@Component({
selector: 'people-list',
template: `
<!-- HERE! Spinner!! -->
<section *ngIf="isLoading && !errorMessage">
Loading our hyperdrives!!! Retrieving data...
</section>
<ul>
<!-- this is the new syntax for ng-repeat -->
<li *ngFor="let person of people">
<a href="#" [routerLink]="['/persons', person.id]">
{{person.name}}
</a>
</li>
</ul>
<section *ngIf="errorMessage">
{{errorMessage}}
</section>
`,
})
export class PeopleListComponent implements OnInit {
people: Person[] = []
errorMessage: string = ''
isLoading: boolean = true
constructor(private peopleService: PeopleService) {}
ngOnInit() {
this.peopleService
.getAll()
.subscribe(
/* happy path */ p => (this.people = p),
/* error path */ e => (this.errorMessage = e),
/* onComplete */ () => (this.isLoading = false)
)
}
}
We can test how our error handling is going to work by simulating a problem in the PeopleService
. Let’s throw an error within the mapPersons
function (You can also misspell the API resource name to potato
to get a HTTP 404 not found error but I thought the force choke was way cooler):
function mapPersons(response: Response): Person[] {
throw new Error('ups! Force choke!')
// The response of the API has a results
// property with the actual results
// return response.json().results.map(toPerson)
}
If you now take look at your app in the browser (run ng serve
if you haven’t already) you’ll be able to see how both the console and the UI show the following message: ups! Force choke!
(If you misspelled the API resource name you’ll see a 404 in the console - an error at a low level of abstraction - and a humanly readable error in the screen “Yikes! There was a problem with our hyperdrive device and we couldn’t retrieve your data!”).
Excellent! Now you can remove the fake error and continue forward… Wouldn’t it be nice if we could just push our Star Wars persons directly into the template whenever they are available? Instead of manually calling subscribe
and setting the people
property?
Well, we can do that with the async pipe!
The Async Pipe
Using the async
pipe (remember that a pipe is the new term for AngularJS filters) you can simplify the example above by:
- Exposing a
people: Observable<Person[]>
property in your component - Setting its value using the
peopleService.getAll
method - Using the
async
pipe in the template
@Component({
selector: 'people-list',
template: `
<ul class="people">
<li *ngFor="let person of people | async " >
<a href="#" [routerLink]="['/persons', person.id]">
{{person.name}}
</a>
</li>
</ul>
`,
})
export class PeopleListComponent implements OnInit {
people: Observable<Person[]>
constructor(private peopleService: PeopleService) {}
ngOnInit() {
this.people = this.peopleService.getAll()
}
}
Which simplifies the logic of the PeopleListComponent
a lot and makes the asynchronous code look pretty synchronous.
Note that I have removed the error handling and the is loading...
message for the sake of simplicity. You could re-instate them by subscribing to the observable as we did in the previous example.
Still not convinced about Observables? Are you missing the olde good times with promises? Don’t worry, because you can still use promises if that’s what you like.
Converting Observables to Promises
Some time, somewhere up there I mentioned the fact that RxJS comes with a lot of superuseful operators that can help you orchestrating complex async applications. It also comes with an operator to convert observables into promises: the toPromise
operator.
Using the toPromise
operator you can convert Observables to Promises within your data access services and continue enjoying the use of Promises from your components. For instance, we could rewrite the getAll
method as follows:
Injectable()
export class PeopleService {
private baseUrl: string = 'http://swapi.co/api'
constructor(private http: Http) {}
getAll(): Promise<Person> {
return this.http
.get(`${this.baseUrl}/people`, { headers: this.getHeaders() })
.toPromise()
.then(mapPersons)
.catch(handleError)
}
// other code...
}
function handleError(error: any) {
// log error
// could be something more sofisticated
let errorMsg =
error.message ||
`Yikes! There was a problem with our hyperdrive device and we couldn't retrieve your data!`
console.error(errorMsg)
// instead of Observable we return a rejected Promise
return Promise.reject(errorMsg)
}
And then consume the promises being returned from any of our components like you are accustomed to in AngularJS and other frameworks.
Want to Learn More About Angular 2 HTTP and RxJS?
If you read the article and are curious about RxJs you’re in luck because I’ve written a couple of articles about it:
And here are some great articles on RxJs and Angular 2 http story from across the interwebs:
- Angular 2 HTTP client from the Angular 2 docs
- Angular 2 Http service from the Angular 2 docs
- Introduction to RXjs in Angular by gerard sans
- Introduction to Reactive Extensions from RX docs.
- RxJS documentation
- Design guidelines for using RxJs in your applications
- Reactive Extensions in Angular 2 a great video with Rob Wormald
- Introduction to RXjs and Reactive Programming course at egghead.io with Andre Staltz
- Taking advantage of Observables in Angular 2 from the Thoughtram blog
Concluding
And that was it my friend! Today you’ve learned about the new async paradigm used in Angular 2, observables, and how it’s used by the new http module. You’ve also learned how to do error handling with Observables and how to take advantage of the async
pipe to simplify your component’s code. Finally you discovered how you can still continue using promises if that’s what you like by taking advantage of the toPromise
operator.
This article wraps up this introductory series to Angular 2. I really hope that you have enjoyed it! Expect more in-depth articles coming up in the near future :)
Would you like to continue learning about Angular?
Then check this other series:
- Building Beautiful Web Apps With Angular Material
- From Idea To Reality in Under 50 Minutes With Angular And Firebase
- TypeScript: JavaScript + Types = Awesome Developer Productivity
Hope you like them!
Written by Jaime González García , dad, husband, software engineer, ux designer, amateur pixel artist, tinkerer and master of the arcane arts. You can also find him on Twitter jabbering about random stuff.