Multiple JSON Data Sources for AngularJS
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.
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.
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.
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.
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.
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
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
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:
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 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.
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
I hate to even use the word “scope” here, because AngularJS has a property called
$scope and sometimes it’s referred to as
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.
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
And voila! Assuming that I haven’t completely screwed up my explanations, you should be able to figure out how I did this.
Hopefully this will match up with someone else’s use case. Remember to read those docs about the