Angular JS logo

Angular 2: HTTP, Observables, and concurrent data loading

Filed under:

Metal Toad is an AWS Managed Services provider. In addition to Angular work we recommend checking out our article on how to host a website on AWS in 5 minutes.

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.

Update, November 27, 2017: This post explains the Http service used in Angular 2. This is now deprecated in favor of the newer HttpClient. This post will remain here as long as Angular 4.x is in long term support. If you are using Angular 5, you should upgrade to the newer HttpClient, as outlined in the post "Angular 5: Making API calls with the HttpClient service".

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!

Learn more about JavaScript in our JavaScript Blog Archive.

Date posted: January 5, 2016

Comments

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."

Same here. Would have saved about several hours' worth of tracing down the map() method... If only I read this article earlier... [cry]

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.

Thanks! Keeping up with Angular 2 Alpha => Beta for anything beyond the angula.io tutorials has been a real chore.

This is the first useful post on creating an http service in Angular 2 beta.

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.

I have the same question. Have you found any good documentation about it?

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')
      );
  }

this example is more complex than the other and you are increasing the cyclomatic complexity of the code.

Same here. Would have saved about several hours' worth of tracing down the map() method... If only I read this article earlier... [cry]

Solve mi problem, from Promise to Observer, for init my data...

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

Please check if you included all the libraries [http?]

Thanks for the great post ... nice walk thru and examples!

Thank you very much for this post

Made a lot of things clear

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.

Shouldn't the AppComponent class import and implement the OnInit interface

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 .

This tutorial is very useful in starting with HTTP services using Angular2.

looks like the npm async package. nice

no me reconoce la función .map

Can u teach the post method in angular2

You explained well, better than the documentation.

thank you for saving my time and hair.
xxo

Great job. This post give sthe understanding of http in angular 2

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!!!!
}

Thank You Very Much

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

Thanks for pointing this out. That is definitely an unauthorized copy of my article.

Great article. As for the unauth copy, I see they took it down, kudos! Now we just need to track down who keeps getting copied on their "Shopping Cart" article since according to about 20 blogs it's apparently the only use for a behaviorsubject! :)

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!

Very knowledgeable, thank you very much.

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

I need to continue even if single request fails(consider other request returning data...). how to achieve this?

how to get the data inside the book on component page. plz help me to find this sol...

Hello all,
How i can call such url : localhost:8080/proj_name/api/getUser

I want to use it with rest api. i am new in angular2.

Thanks!

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.

Thanks for all the help, but CORS is the area where most of the people will be stuck . It will be good if we get help here.

If it wasn't for this article, I would have been spending another day deciphering the documentation.

Finally A Good Article about Observables and the Http service. Thanks Alot ..

Thanks, with your forkJoin demo I can init my form easyli !

Hi , I just have one doubt , let say i have books as ["the diary" , "the notebook"]. which is shown in template as well.

Now , if i update "the notebook" and make it "the day" manually in books.json file at runtime. Then will it reflect on template

Thanks

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.

Metal Toad is an Advanced AWS Consulting Partner. Learn more about our AWS Managed Services

Schedule a Free Consultation

Speak with our team to understand how Metal Toad can help you drive innovation, growth, and success.