Angular 2: HTTP, Observables, and concurrent data loading

Angular 2 provides a new pattern for running asynchronous requests, called Observables. Here, we will review a few of the concepts and eventually see how to run multiple concurrent HTTP requests, with the callbacks running only after all of them have completed.

Author's note, November 24, 2016: Updated for final Angular 2.0 API. Updated on June 5, 2017.

About Observables and the Http service

Angular 1 developers should be familiar with using Promises to load data asynchronously. Angular 2 uses an a more advanced pattern called Observables. These are objects which can emit one or more data packets. Other objects can subscribe to these Observables and run a callback each time data is emitted. (In this example using the Http service, each Observable will only emit data once, but a different type of Observable could emit data more than once.)

The Observable classes in Angular 2 are provided by the ReactiveX library.

The Http service in Angular 2 is the successor to Angular 1's $http. Instead of returning a Promise, its http.get() method returns an Observable object.

Try this at home!

The source code for this demo application is available on GitHub. It's recommended that you try the Angular 2 Tutorial first, for a basic overview of Angular 2 architecture and Typescript.

For brevity, I have not listed the contents of the JSON data files in this post. They are available in the GitHub repository if needed.

Getting started

To use the HTTP service and Observables, we need to add a few logistics to our index.html and Angular 2 bootstrap file.

index.html:

<html>
  <head>
    <title>Angular2 Http Demo</title>
    <script src="node_modules/zone.js/dist/zone.js"></script>
    <script src="node_modules/reflect-metadata/Reflect.js"></script>
    <script src="node_modules/systemjs/dist/system.src.js"></script>
    <script src="systemjs.config.js"></script>
    <script>
      System.import('app').catch(function(err){ console.error(err); });
    </script>
  </head>
  <body>
    <demo-app>Loading...</demo-app>
  </body>
</html>

systemjs.config.js:

(function (global) {
  System.config({
    paths: {
      // paths serve as alias
      'npm:': 'node_modules/'
    },
    // map tells the System loader where to look for things
    map: {
      // our app is within the app folder
      app: 'app',
 
      // angular bundles
      '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
      '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
      '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
      '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
      '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
      '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
      '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
      '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
      '@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js',
 
      // other libraries
      'rxjs':                      'npm:rxjs'
    },
    // packages tells the System loader how to load when no filename and/or no extension
    packages: {
      app: {
        main: './main.js',
        defaultExtension: 'js'
      },
      rxjs: {
        defaultExtension: 'js'
      },
    }
  });
})(this);

Notice the 'rxjs' configuration in systemjs.config.js. This provides the context necessary to use some of our import statements in our Typescript files.

App/main.ts:

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
 
platformBrowserDynamic().bootstrapModule(AppModule);

App/app.module.ts:

import { NgModule, CUSTOM_ELEMENTS_SCHEMA }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpModule }    from '@angular/http';
import {FormsModule} from '@angular/forms';
import {DemoService} from './demo.service'
 
import { AppComponent }  from './app.component';
 
@NgModule({
    imports: [BrowserModule,HttpModule,FormsModule],
    declarations: [AppComponent],
    providers: [DemoService],
    schemas: [CUSTOM_ELEMENTS_SCHEMA],
    bootstrap: [AppComponent]
})
export class AppModule { }

Here is where we inject our app-level dependencies.

App/app.component.ts

Our demo app contains only one simple component, which contains a few elements to display some simple data. We will be loading data from a few JSON files, to simulate an API call.

import {Component} from '@angular/core';
import {DemoService} from './demo.service';
import {Observable} from 'rxjs/Rx';
 
@Component({
  selector: 'demo-app',
  template:`
  <h1>Angular2 HTTP Demo App</h1>
  <h2>Foods</h2>
  <ul>
    <li *ngFor="let food of foods">{{food.name}}</li>
  </ul>
  <h2>Books and Movies</h2>
  <h3>Books</h3>
  <ul>
    <li *ngFor="let book of books">{{book.title}}</li>
  </ul>
  <h3>Movies</h3>
  <ul>
    <li *ngFor="let movie of movies">{{movie.title}}</li>
  </ul>
  `
})
export class AppComponent {
 
  public foods;
  public books;
  public movies;
 
  constructor(private http: Http) { }
 
}

Executing a single HTTP request

We can use the HTTP service to request a single resource, by using http.get. This is similar to Angular 1. To do this, we add the following code to our app.component:

  ngOnInit() {
    this.getFoods();
  }
 
  getFoods() {
    this.http.get('/app/food.json')
      .map((res:Response) => res.json())
      .subscribe(
        data => { this.foods = data},
        err => console.error(err),
        () => console.log('done')
      );
  }

We keep the constructor simple here. All it does is initialize the http variable. We'll use the ngOnInit() hook to start the data loading.

Just like Angular 1, we use http.get() to run our HTTP request. This returns an Observable object, which gives us methods like map() for configuring the data processing, and subscribe() for observing the output.

Angular doesn't yet know that we want to parse the response as JSON. We can let it know this by using the .map((res:Response) => res.json())call. This also returns an Observable, useful for method chaining.

To receive the output, we call the subscribe() method. This takes three arguments which are event handlers. They are called onNext, onError, and onCompleted. The onNext method will receive the HTTP response data. Observables support streams of data and can call this event handler multiple times. In the case of the HTTP request, however, the Observable will usually emit the whole data set in one call. The onError event handler is called if the HTTP request returns an error code such as a 404. The onCompleted event handler executes after the Observable has finished returning all its data. This is less useful in the case of the Http.get() call, because all the data we need is passed into the onNext handler.

For more information about the Observable object, see the ReactiveX documentation.

In our example here, we use the onNext handler to populate the component's 'foods' variable.

The error handler just logs the error to the console. The completion callback runs after the success callback is finished.

The handler functions are optional. If you don't need the error or completion handler, you may omit them. If you don't provide an error handler, however, you may end up with an uncaught Error object which will stop execution of your application.

Executing multiple concurrent HTTP requests

Many times, we need to load data from more than one source, and we need to delay the post-loading logic until all the data has loaded. ReactiveX Observables provide a method called forkJoin() to wrap multiple Observables. Its subscribe() method sets the handlers on the entire set of Observables.

To run the concurrent HTTP requests, let's add the following code to our component:

  ngOnInit() {
    ...
    this.getBooksAndMovies(); // <-- add this line
  }
 
  getBooksAndMovies() {
    Observable.forkJoin(
        this.http.get('/app/books.json').map((res:Response) => res.json()),
        this.http.get('/app/movies.json').map((res:Response) => res.json())
    ).subscribe(
      data => {
        this.books = data[0]
        this.movies = data[1]
      },
      err => console.error(err)
    );
  }

Notice that forkJoin() takes multiple arguments of type Observable. These can be Http.get() calls or any other asynchronous operation which implements the Observable pattern. We don't subscribe to each of these Observables individually. Instead, we subscribe to the "container" Observable object created by forkJoin().

When using Http.get() and Observable.forkJoin() together, the onNext handler will execute only once, and only after all HTTP requests complete successfully. It will receive an array containing the combined response data from all requests. In this case, our books data will be stored in data[0] and our movies data will be stored in data[1].

The onError handler here will run if either of the HTTP requests returns an error code.

Refactoring the data loading into a Service

Now that we have seen the entire lifetime of an Observable object, we can refactor part of the logic into a Service for reusability.

I find it useful for the Services to return an Observable, rather than the final data. This allows the component to subscribe to the Observable and either populate the local data variables or show an error message to the user.

First, we create our new Service, called DemoService:

import {Injectable} from '@angular/core';
import {Http, Response} from '@angular/http';
import {Observable} from 'rxjs/Rx';
 
@Injectable()
export class DemoService {
 
  constructor(private http:Http) { }
 
  // Uses http.get() to load a single JSON file
  getFoods() {
    return this.http.get('/app/food.json').map((res:Response) => res.json());
  }
 
  // Uses Observable.forkJoin() to run multiple concurrent http.get() requests.
  // The entire operation will result in an error state if any single request fails.
  getBooksAndMovies() {
    return Observable.forkJoin(
      this.http.get('/app/books.json').map((res:Response) => res.json()),
      this.http.get('/app/movies.json').map((res:Response) => res.json())
    );
  }
 
}

This is the same logic that we formerly had in our component. However, notice that the methods of this service return Observable objects. In our AppComponent, we will subscribe to the onNext, onError, and onComplete callbacks.

After creating our new service, we need to inject it into the application in boot.ts:

...
import {DemoService} from './demo.service' // <-- add this line
 
bootstrap(AppComponent, [
  HTTP_PROVIDERS,
  DemoService  // <-- add this line
]);

Our refactored AppComponent

import {Component} from '@angular/core';
import {DemoService} from './demo.service';
 
@Component({
  selector: 'demo-app',
  template:`
  <h1>Angular2 HTTP Demo App</h1>
  <h2>Foods</h2>
  <div *ngIf="foods_error">An error occurred while loading the foods!</div>
  <ul>
    <li *ngFor="let food of foods">{{food.name}}</li>
  </ul>
  ...
  `
})
export class AppComponent {
 
  ...
  public foods_error:Boolean = false;
 
  constructor(private _demoService: DemoService) { }
 
  ...
 
  getFoods() {
    this._demoService.getFoods().subscribe(
      data => { this.foods = data},
      err => { this.foods_error = true }
    );
  }
 
  getBooksAndMovies() {
    this._demoService.getBooksAndMovies().subscribe(
      data => {
        this.books = data[0]
        this.movies = data[1]
      }
    );
  }
}

Notice that we are now injecting the DemoService into the constructor, instead of the Http object. We also need to import DemoService at the top of the file, instead of Http. We then call methods of this._demoService instead of this.http.

Also notice that we have added an error message in the template. This will be hidden unless the Observable's onError handler runs.

Happy coding!

Thanks for this.

"Note: Many alpha releases of Angular 2 included the map() method automatically. This was later removed from the automatic import. The beta releases require this to be imported separately."

Thanks for presentation of forkJoin which is interesting.
But let's imagine a case a little more complicated : we have to do one action when movies are loaded, and another when movies and books are loaded. How to do this ? For the action related to movies and book, we can use forkJoin. But for the action only related to movies, we would need to subscribe to first observable. Not sure, then, that the observable can be reuse next in a forkJoin. What do you thinnk ?

I think you can use output binding and eventEmitter for this. Attach this to template (clicked) = "OnItemClick($event)" and in compnent class, call a method that handles the event.
OnItemClick(movieId: number)
{
return $this->_demoService->getMovie(movieId);//this is a specific method in the Demoservice to get more info about a movie
}

i think we can have multiple subscriber to same observable. so first register a callback function on the observable then pass it to fork join. i think that will work for you.

How would I execute the two HTTP requests sequentially instead of currently? Let's say request 1 returns a list of books and request 2 should fetch the book details of one them.

Subscriptions has 3 callbacks, the last one being OnComplete... Also, promises have a .then() function that allows you to create sequences. Alternatively you can watch for the .wait() function in es6. Hope this helps ;)

forkJoin() isn't designed for this use case. You would be better off chaining the calls by placing one call inside the callback of the other. Something like the following should work:

getFoods() {
    this.http.get('/app/food.json')
      .map((res:Response) => res.json())
      .subscribe(
        data1 => {
          this.foods = data;
          // make second API call
          this.http.get('/app/another-endpoint.json')
            .map((res:Response) => res.json())
            .subscribe(
              data2 => { this.another_variable = data2},
              err => console.error(err)
            );
        },
        err => console.error(err),
        () => console.log('done')
      );
  }

I copied the same exact code for my application . The onNext method is never executed. The data and request is being returned. It does execute the OnError function. Im not sure what Im doing wrong. Im not getting any errors. It just ignored the OnNext functions

Observable.forkJoin([
this.service.getItems('test')]).subscribe(
data => {console.log(data);} , <--------- this never gets executed
err => console.error(err) <----- this gets executed

Hi, thank you for this tutorial!

I wanted to know if there's a particular http function that allows you to know if data is still being fetched, so as to for example display a 'loading' screen until the data is retrieved.

This would be useful for building a progress meter. Observables can emit multiple data packets, so what you propose is possible in theory.

However, the Http class always seems to emit its data in one packet, meaning it doesn't natively support what you propose. It seems that you would need to create your own custom class implementing the Observable pattern.

Not sure if everyone is onbaord with me, wasted 2 days getting experimenting http. And this is the first useful service. Thanks atone
please put some more articile / github code .

You explained well, better than the documentation.

thank you for saving my time and hair.
xxo

Great job ! Really helpful for understanding and handling the service data in angualr 2!

how can I get the data outside of the get method.

getBooksAndMovies() {
this._demoService.getBooksAndMovies().subscribe(
data => {
this.books = data[0]
this.movies = data[1]
}
);

console.log(this.movies) //is undefined!!!!
}

Nice article so far.
Do you have any example which consumes Http post service with JSON parameter ?
I would be greatful if you provide any example.

Thank you

Did this guy "borrow" your article?
medium.com/@tkssharma/angular-2-http-observables-data-loading-rx-js-9046f6c75824#.nf1ams55m

The article you reference was created in June, this article was created in January.. It would be a neat trick indeed to copy something that wasn't in existence. Be careful before accusing someone of plagiarizing

THANK YOU OH GOD THANK YOU!!!!!!!!!!!!

I used this successfully with a json file, thank you!. But when I tried getting data from a URL and setting the headers. I received an error. It looks like the solution is to set the response headers, but I can't find a way to do this. Could you maybe do a similar tutorial on how to set up CORS requests? The JSONP example at the Angular site is not so relevant.

Thanks!

After researching and testing very much complicated thing I found in the simplicity of this example something that actually works like a charm.

Do you already have the "http://localhost:8080/proj_name/api/getUser" URL running? If so, you can do something like this:

this.http.get("/proj_name/api/getUser").map((res:Response) => res.json());

If you're calling it from another domain (different than localhost:8080) you would need the fully qualified URL:

this.http.get("http://localhost:8080/proj_name/api/getUser").map((res:Response) => res.json());

In this case, you will probably need to set up CORS and whitelist your client address, to avoid errors about the violation of same origin policy. This is done on the server side where you create your API endpoint. I can't help with that because every back-end system is different.

Hi, what if I have php file, that returns json data:

?php
header("Content-type: application/json");
$test_array = array(
'id' => 'Vuk',
'name' => 'Vasic',
'gender' => 'male',
'company' => 'cmp',
'email' => 'eml'
);
echo json_encode($test_array);

?>

How i can access this data?

constructor(http: Http){
http.get('servis.php')
.map(res => res.php())
.subscribe(korisnici => this.korisnici = korisnici);
}

Hi ,

I Download from github and run it my local server. But its showing in console.
It not able to delete food and save new food.
Please help me out.

This example and the GitHub repo don't include any back-end code. I only wrote the front-end code to show you how you would interact with an API. Writing the API itself is out of scope for this article.

If you run the code from GitHub and click the "save" or "delete" buttons, it will make an API call which will fail with a 404. This is expected behavior because there is no back-end code.

I understand that this might not be helpful to everyone. But there are many different back-end technologies and I didn't want to distract from the Angular 2 topics. FWIW, I tested this against a simple API I built using the Django Rest Framework.

Add new comment

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <cpp>, <java>, <php>. The supported tag styles are: <foo>, [foo].
  • Web page addresses and email addresses turn into links automatically.
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.

About the Author

Keith Dechant, Software Architect

Keith has been working on the web almost since it was new, writing his first HTML in 1996 and his first PHP code in 1999. Along the way, he has written everything from e-commerce websites to mailing list software to large web applications for industrial and non-profit organizations. Recently, he has done a lot of work with ASP.NET, Django, Drupal 7 and 8, and AngularJS.

Keith is a native of Illinois who moved to Portland in 2009. He has been to 49 US states, five continents, three former Soviet republics, and two countries that no longer exist. He likes hiking, mountain climbing, classic video games, and the Oxford comma.