Angular2 Observables, Http, and separating services and components

by
Tags: , , , ,

Ok, so you're ready to roll up your sleeves and get something practical done with Angular2. Let's take a look at how to get some data via the new Http service – it's quite a bit different than the Angular 1 $http service semantically, but serves the same purpose.

Setting up the Http service

In our application component, we need to import HTTP_PROVIDERS, and then send it along to our bootstrap function so the injector knows to configure it on the way up. Here's the relevant code:

import {HTTP_PROVIDERS} from 'angular2/http';
  ...
bootstrap(MyAppComponent, [... HTTP_PROVIDERS]);

This is somewhat similar to the Angular 1 way of bootstrapping a library, but in Angular 2 we don't get Http for free as part of a built-in module. Everything is added by you, the developer, when bootstrapping the app or providing a component. Doing this should make applications that don't rely on these libraries boot quicker and also consume less overall memory.

Backgrounder: Injecting the $http service into our controllers/services – Angular 1

Much like Angular 1, there is a bit of a gulf between directly using Http from a component and from a service you build behind the scenes for encapsulating your business logic. In Angular 1, the pattern goes something like this:

// a simple controller using $http
  angular.module('fooApp')
  .controller(function($http, $scope) {
    $http.get('/bar/baz')
    .success(function(data) {
       $scope.results = data;
    })  /** scene missing for rest of this... */
  });

Because the $http.get function in Angular 1 returns a promise, you either need to handle it right there with .success and .error, or if you're creating a service to handle the $http call, you return the promise to the controller like this:

angular.module('fooApp')
  .controller(function(dataService, $scope) {
    dataService.loadBaz()
    .then(
      function(response) { $scope.results = response; },
      function(error) { $scope.hasError = true; ... }
      angular.noop)
  })
  .service('dataService', function($http) {
     this.loadBaz = function() {
       return $http.get('/bar/baz');
     };
  });

I don't want to dwell on Angular 1 here; we have a very comprehensive article on promises and $q for async calls that goes through the thornier parts of this API.

But what I do want to point out is that Angular 1 uses Promises for separation of concerns and async behavior – you emit a promise from a service API, and the caller (usually a controller or directive) registers up to three callbacks – success, error, and notifications. Now there are several problems with promises that are very well documented:

  • You can't cancel a promise in-flight
  • You can't get more than one answer later on from a promise (unless you use the notification method, and the promise is still considered open and in-flight at that point)

So, while this is the way we do it in Angular 1, the Angular 2 team decided a more comprehensive way of dealing with asynchrony was needed for a modern API.

Angular 2 Asynchrony and stream-based processing with Reactive Observables

The Angular 2 team has thrown in to the Reactive programming game by making async calls use the RxJs library from Microsoft. This library, Reactive Extensions for JavaScript, provides an event-driven stream of notifications to a caller by issuing a return type known as an Observable.

The caller typically executes a call to an Angular 2 Service (or component method), and is fed an Observable, which it then can hook a chain of functional programming methods to just like ES6 generators and iterators do, except that the data is coming back asynchronously. Think of it as iterating what you think is a list of results, but these results are fed as they are fetched from the server, even via multiple web calls.

A single-component Observable

In the single-component mode, here's how you use the Http service. We'll start assuming we've bootstrapped the application using the HTTP_PROVIDERS dependency as described above.

Here are the imports required:

import { Component, CORE_DIRECTIVES }
    from 'angular2/angular2';
  import { Task } from '../datatypes/task';
  import { Http } from 'angular2/http';

We'll need Task, which is a simple value object we've defined elsewhere, and we'll need Http – beyond that the Component and CORE_DIRECTIVES imports are required to mount our component.

Next, the component annotation and view template:

@Component({
    selector: 'tasks-component',
    template: `
      <ul>
        <li *ng-for="#task of tasks">
          {{ task.id }} - {{ task.description }}
        </li>
      </ul>
    `,
    directives: [CORE_DIRECTIVES]
  })

You'll note that we are no longer using @View in this example. As of recent builds (Alpha 40?) they've added some of the view annotation properties such as template to @Component to cut down on the number of objects and annotations required.

Also, note the back-ticks for the super handy multi-line embedded templates. These alone are worth their weight in gold.

The iterator (*ng-for="#task of tasks") is similar to Angular 1's ng-repeat statement, just with a different syntax. As with the old ngFor, it creates a nested template data context (think scope but we don't call it that anymore) and stores task within it. The # is shorthand, you would have to specify var task instead. (So is the * in *ng-for but that's for another blog post).

Beyond that, the component annotation is a pretty standard one – we include the CORE_DIRECTIVES array of components so we gain access to ngForOf.

The TaskComponent class

Now, we'll discuss the class definition. I've commented that one inline and will provide some discussion below:

  export class TasksComponent {
    // component state is now member variables, no more $scope
    tasks: Array<Task>;
    // constructors do dependency injection in Angular2
    constructor(http: Http) {
      // our http call here
    }
  }

Let's step through what a component would have to do if it made the http.get call itself and processed the results, putting them in the tasks collection.

The Http get method

The get method of http (from the angular/http/Http class) creates an Observable object.

http.get('/api/v1/tasks.json')

In this 'all-in-one' case where the entire process is performed from a @Component, we work with the observable directly, telling Angular how we should process the results.

Mapping the initial result to JSON

We begin our processing by mapping the result object (providing a result object for the incoming request) using map:

.map(res => res.json())

The map function processes a result from the observable (in our case, the fetched payload of an http.get call), using an ES6 arrow function.

The arrow function's parameter res is actually a ResponseData object, and can be parsed as binary (blob()), string (text()) or, in our case, JavaScript via JSON (json()) content via its helper methods.

So in this way, we're already transforming the output from a raw HTTP Response to a JavaScript object. This is, indeed, a step we don't normally take from Angular 1, but it makes our content a bit more flexible to deal with.

Second-level transformation into Value Objects

We can further transform with a second map request, as each map mutates the response and provides a more specific observable:

.map((tasks: Array<any>) => {
      let result:Array<Task> = [];
      if (tasks) {
        tasks.forEach((task) => {
          result.push(new Task(task.id, task.description,
                                task.dueDate, task.complete));
        });
      }
      return result;
     }
  })

In this mapping method, we receive the array of JSON results (hence the type <any>), create a typed array of our output type (Task, which we'll show below), and iterate through them with forEach. In TypeScript + Angular 2, we don't need to worry about our browser providing the Array.prototype.forEach method, since we'll be using the es6-shim for that.

After we're done with our mappings, we can subscribe to the observable, handing the answer off to our view by binding the result to the tasks property of the class instance:

 .subscribe( res => this.tasks = res);

As you might have guessed, this is a one-shot observable. It only does one request, and then it completes. Later on, we’ll see how to emit multiple events, using almost the same client code.

The entire component body

Here is the entire component body, with comments inline:

export class TasksComponent {
    // component state is now member variables, no more $scope
    tasks: Array<Task>;
    // constructors do dependency injection in Angular2
    constructor(http: Http) {
      // our http call here
      // make the call
      this.http.get('/api/v1/tasks.json')
      // initial transform - result to json
      .map( (responseData) => {
        return responseData.json();
      })
      // next transform - each element in the
      // array to a Task class instance
      .map((tasks: Array<any>) => {
          let result:Array<Task> = [];
          if (tasks) {
          tasks.forEach((task) => {
            result.push(new Task(task.id, task.description,
                                 task.dueDate, task.complete));
          });
        }
      })
      // subscribe to output from this observable and bind
      // the output to the component when received
      .subscribe( res => this.tasks = res);
    }
  }

Here is the Task object:

export class Task {
    id: number;
    description: string;
    dueDate: Date;
    complete: boolean;
    completedDate: Date;
    constructor(id: number,
                description: string,
                dueDate: Date,
                complete: boolean) {
      this.id = id;
      this.description = description;
      this.dueDate = dueDate;
      this.complete = complete;
    }
    setComplete() {
      this.complete = true;
      this.completedDate = new Date();
    }
  }

Separating the Component and its data Services

So now you might be thinking "This is fine, but we started this way prototyping Angular 1 code, and we moved to services." You're right, you did. And the process now is very similar. Let's move the Http code to a service.

Creating a Service in Angular2 – the TaskService

First, we'll look at the service itself. We'll move the imports over:

import { Http } from 'angular2/http';
  // normally this would be imported from 'angular2/core'
  // but in our compiler we're pulling the dev version of angular2
  import { Injectable } from 'angular2/angular2';
  import { Task } from '../datatypes/task';

The new import here is Injectable – we're going to use that one to annotate our service class, so it can be used for dependency injection. Read this excellent article on why we do that.

@Injectable()
export class TaskService {

We export the service class so we can import it later in the controller. This is how simple ES6 module syntax is: export a class from a file, then use import { ClassName } from 'filename'; to import it later.

Dependency injection in the TaskService

Now we just use the constructor to inject the dependency. One way to do it is this:

   constructor(public http: Http) {
      console.log('Task Service created.', http);
    }

Pretty easy. Another way to do it would be to use the @Inject annotation on each injected constructor parameter, but this one is cleaner. Again another thing the ThoughtRam article above will go into detail about.

Provide the method to observe

Now, we'll see how we do the service-side call – we just literally return the result of the last map call – which is still an Observable – back to the caller.

  getTasks() {
      // return an observable
      return this.http.get('/api/v1/tasks.json')
      .map( (responseData) => {
        return responseData.json();
      })
      .map((tasks: Array<any>) => {
        let result:Array<Task> = [];
        if (tasks) {
          tasks.forEach((task) => {
            result.push(
                       new Task(task.id,
                                task.description,
                                task.dueDate,
                                task.complete));
          });
        }
        return result;
      });
    }
  } // end TaskService class

Now, our refactored Component

If we're role playing Angular 1 -> Angular 2 migration here, the Component takes the place of our Controller and View. So we'll inject the TaskService into the controller and use the getTasks() method, and subscribe to the call results. The entire component is listed here because by now you should see the simplification:

import { Component, CORE_DIRECTIVES } from 'angular2/angular2';
import { TaskService } from '../services/task-service';
import { Task } from '../datatypes/task';
@Component({
  selector: 'tasks-component',
  providers: [TaskService],
  template: `
    <ul>
      <li *ng-for="#task of tasks">
        {{ task.id }} - {{ task.description }}
      </li>
    </ul>
  `,
  bindings: [TaskService],
  directives: [CORE_DIRECTIVES]
})
export class TasksComponent {
  tasks: Array<Task>;
  constructor(public taskService: TaskService) {
    // now it's a simple subscription to the observable
    taskService.getTasks()
      .subscribe(res => this.tasks = res);
  }
}

Why Observables are better for Angular2

It should become apparent that Observables provide greater power to the developer. You can decide where you break the chain and export the observable, and both local and extra-method observables work in exactly the same way. But there are some really nifty additional features at work here.

I'm about to paraphrase / lift some code ideas from Rob Wormald's talk – Everything is a stream. To really dig into what Angular 2 and observables can do, watch that talk.

For example, you can ask the server to retry your call several times:

...
   return this.http.get('/api/v1/tasks.json')
      .retry(5)
      .map( res => res.json());
      ...

In addition you can poll for results – by using Observable.interval:

...
    pollTasks() {
     return Observable.interval(10000)
      .flatMapLatest(() => http.get('/api/v1/tasks.json'))
      .map(res => res.json())
    }
    // caller can do subscription and store it as a handle:
    let tasksSubscription =
      pollTasks()
        .subscribe( data => this.payload = data);
    // turn it off at a later time
    tasksSubscription.unsubscribe();

What the what? Ok, so the pollTasks() method emits a call every 10 seconds, which triggers the call inside of flatMapLatest – we're basically ignoring the result of that event, and using it to trigger the http.get method to fetch our data. We'll map it into JSON each time.

The caller of our pollTasks() method just gets an observable, which is emitting content every 10 seconds. To use it, the subscriber subscribe()s as usual, only this call is happening every 10 seconds, not once. You should turn it off once you don't care anymore, hence the unsubscribe() method.

Also the flatMapLatest call is really interesting – any calls generated by previous events are canceled, including their in-flight HTTP method calls. Try doing that with a promise!

Where to find samples?

First, take a good look at the Angular 2 repository on GitHub. They have recently moved all of their sample code to a playground module, and if you view the http sample you'll get a good start.

I'm working on a routing example for a personal information manager, which will eventually get more feature-rich (as in, um, actually function). It's over at github.com/krimple/angular2-router-demo-pim and it is the source of the code examples in this article. An article on routing will appear shortly based on this code base.

Finally, any questions about Angular2, observables, and other features get answered pretty well and quickly at the Angular2 Gitter page – sort of a Github-based IRC.