Multiple JSON Data Sources for AngularJS

AngularJS full logo

Edit - This was written before Google announced that they were going to build the next version of Angular differently from the ground up, it wouldn’t be backward-compatible, and anyone who built big apps with the first version were suckers.

I would delete this post entirely, but there are companies out there who already have apps that they can’t abandon the way Google abandoned them. If this post helps anyone learn how to work on those legacy apps, then it’s worth leaving it up.

If you have more than one data source for your AngularJS app, then you’ll need them all completed before the thing runs. Although this is a common problem, solutions can be hard to find on the Unhelpful Interwebs. But don’t worry, I promise we can solve it. (See what I did there?)

The Problem is Data

There are plenty of places where people ask about this, but there never seems to be an answer that fits my use case or works without first writing other code that no one will specify. And of course, there are people who seem to delight in missing the point of a Stack Overflow post and repeatedly answer a question that the original poster didn’t ask. So I’m just going to tell you my use case right now, and hopefully it matches up with what you’re doing.

I have two different data sources that I want to pull before AngularJS does anything. I want to pull them from the Intertubes, do some stuff with them and then present them as one big data object which I will store in a service. The data doesn’t change often, so I don’t want to bother the server every time I change views or something.

I also don’t want to use jQuery because it’s bad for early AngularJS users. You can end up doing things the jQuery way, and that will send you down a rabbit hole of errors to solve which have nothing to do with your goals. This can be avoided if you start with just AngularJS and then add the jQuery stuff later when you’re more comfortable.

It took me a bit of digging, but I finally worked out a solution. I have a way to get multiple data sources loaded before your AngularJS app runs away without you and slings errors all over the place like an angry toddler.

Promises, Promises…

Much of this depends upon promises, which are kind of hard to explain without going deep into geekspeak. The general idea is that you promise to do something later. You tell the browser to run a function, but only after some data comes back from the server. This sort of happens in jQuery’s success() callback for it’s ajax() method, but deferred objects are a better example.

So anyway, you might have an init() function that should only kick off when two AJAX calls return data from the server. Except that the server is going to give you JSON in whatever format it wants, which is different from what you need. You don’t get to change this. For the purpose of this example, you do not control the data. Does everyone hear that? You do not control the data. At all. Not even a bit.

I say this because there’s always (at least) one jackass on Stack Overflow who reads “I don’t control the data” and then tells people to change the server-side code so the data will be better. Then the original poster has to repeat “I don’t control the data” over and over, and the guy still doesn’t listen. People like this will go to the Special Hell.

Shepherd Book from the Firefly TV Show
You can also go there for talking at the theater.

But I digress. The point is that you have to pull in two different AJAX calls and transform/combine the data into what you need for your app. If you want to skip the explanation and just see the results then you can check out this fiddle. Otherwise, let’s talk about the code.

Not for First-Day Beginners

This is not for beginning AngularJS developers. I’m just showing you how I got past a particular problem. To follow it,  need to be familiar enough to make sense of the online documentation, which I managed by taking courses from Code School. The first course is free, but I strongly recommend taking the second course as well. One month of membership costs less than many of the books I’ve bought over the years.

Starting the App

There’s plenty of documentation in the comments for this fiddle, but I’ll go over the high points here.First I’ll start by declaring my module. Nothing fancy here. A larger application might have stuff going on in this first bit, but I’m just doing a couple of little things with a service and a controller.

angular.module("myApp", []);

After declaring the module, I made a service factory and a controller. The service factory is a bit different from the basic service because you can do stuff before returning the final object. I didn’t in this case, but I still used a factory because I want to become more familiar with their use.

The Service

I’m writing the service and controller as if they’re in separate files, which is why you can see me declaring the module again. But this doesn’t create a duplicate. The module already exists at this point, so this is just a reference to it.

/* Service: This would normally be in another file. */
angular.module('myApp')
.factory('myService', ['$http', '$q', function myServiceFactory($http, $q) {
  /* Because this is a factory, you can do code stuff here before you return. */
  return {
    getData: function() {
      var BostonURL = "http://maps.googleapis.com/maps/api/geocode/json?address=Boston%20MA";
      var NewYorkURL = "http://maps.googleapis.com/maps/api/geocode/json?address=New%20York%20NY";
 
      var defer = $q.defer(); 
      var BostonData = $http.get(BostonURL, { cache: 'true'});
      var NewYorkData = $http.get(NewYorkURL, { cache: 'true'});
 
      $q.when( $q.all([BostonData, NewYorkData]) ).then(function(data) {
        finalData = [data[0].data.results[0], data[1].data.results[0]];
        defer.resolve(finalData);
      });
      return defer.promise;
    }
  }
}]);

This is where the magic happens. I have two different URL’s going to Google for some simple city information. Those are both being used by the $http service with cache enabled. Any subsequent calls to the service with the exact same url (including parameters) will be pulled from a cache object instead of going to the Internet to retrieve the exact same information again. Either way, the $http service returns a promise rather than a set result. For details on this, go to the docs page for cache and do a Find for “Caching”.

The next bit is the .when() method of the $q service.

Picard and Q in the Ready Room
No, not this one. He didn’t provide much service.

This is where promises and deferred things come into play, so it’s worth a trip to the docs page for the $q service to see how things are put together. Do a Find for “when(” to see the method and its arguments. The when().then() combo sets up a promise and a function to run when it resolves. Inside that, I use the all() method to list my url promises. BostonData doesn’t represent the data for Boston. It represents a promise for that data. The all() method will resolve when everything it’s waiting for has completed on way or another. When that’s done, the when() will resolve, and that in turn will fire the function for then().

This is confusing stuff, but it’s worth learning. The general idea is that I have a bunch of promises that only resolve when other, previous promises resolve. I could have any number of data calls inside that all() method, and it wouldn’t resolve until all of them are completed or rejected.

So after all of this crazy stuff is done, I’m left inside the then() function. By this point, everything is done and I just need to dress up the data and return it. For that, I need one last promise. It’s the one I declared at the top, like so:

  var defer = $q.defer();

This is a generic deferred object, and it doesn’t rely upon any data. I’m going to set this one to resolve on my own once I get into that then() function. After I turn the data from both url’s into an array of two city names, I tell the defer variable to resolve. I’m going to tell my controller to wait for this, and to do its stuff only after this is finished.

The Controller

The controller uses the array syntax so I can reference the $scope object and my service. The array syntax looks weird, but it’s useful because not using it can sometimes cause things to go wacky when your code is minified. There might be other reasons, but I didn’t research that any further because I know that the syntax always works and some of the experts I’ve read from prefer it. I’ve decided to make it a standard for my AngularJS work.

To sum up the syntax, you add all of your dependencies as strings, and then you put in a function as the last thing in the array. That function gets all of the other stuff you’ve named as regular variable arguments. You can see below that “myService” is a string at first, and then it’s not in quotes when used as an argument. Oh, and don’t forget the closing square bracket at the end.

/* Controller */
angular.module('myApp')
.controller('MyCtrl', ['$scope', 'myService', function($scope, myService) {
  var that = this;
  myService.getData().then(function(data){
    that.cities = data;
  });
}]);

I’ve set a single property for this controller called cities, because I know that I’m going to serve up some city names. I’ve cached “this” to a variable called that because I don’t want to worry about JavaScript scope. You don’t always have to do this, but it doesn’t hurt anything and I’m not writing something so massive that the sky will fall if I have one extra line. I like to know that I’m still referencing the controller, and I like to be able to set properties for it even if I’m inside a function within its scope.

I hate to even use the word “scope” here, because AngularJS has a property called $scope and sometimes it’s referred to as scope and if you get them confused in an explanation then the reader is screwed. Just to be clear, in the previous paragraph I was just talking about JavaScript scope. That’s what governs what this will reference in a particular situation.

The controller waits for the data to come back because all of its functionality lives inside the then() function. This is why that confusing heap of promises were important. Nothing in this controller will cause any trouble until that defer object resolves.

The Markup

So now I just need some HTML to tie all of this together. There isn’t much to it, really. I have my app, my controller and a basic use of the ng-repreat directive.

<div ng-app="myApp" class="container">
  <ul ng-controller="MyCtrl as ctrl">
    <li ng-repeat="city in ctrl.cities"></li>
  </ul>
</div>

And voila! Assuming that I haven’t completely screwed up my explanations, you should be able to figure out how I did this.

Or you can just look at the fiddle. As I mentioned, I put a bunch of documenting comments into that thing.

Hopefully this will match up with someone else’s use case. Remember to read those docs about the $q service.