Barbarian Meets Coding Titlebarbarianmeetscoding

WebDev, UX & a Pinch of Fantasy

angular2

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!

Angular 2 Getting Started

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 like get, post, put, etc
  • Response is a type that represents a response from an HTTP service and follows the fetch API specification
  • Observable 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 to rxjs map operator. rxjs is a very big library with lots of operators and features. A best practice is to only import the parts of rxjs that you need to use and that’s why we start by only importing the map 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:

  1. Exposing a people: Observable<Person[]> property in your component
  2. Setting its value using the peopleService.getAll method
  3. 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:

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:

Hope you like them!


Jaime González García

Written by Jaime González García , Dad, Husband, Front-end software engineer, UX designer, amateur pixel artist, tinkerer and master of the arcane arts. You should follow him on Twitter where he shares useful stuff! (and is funny too).Jaime González García