In AngularJS, multiple HTTP requests can be coordinated with $q.all, making your project's code cleaner and more testable.
We've got a series of async HTTP requests to the GitHub API. To grab data we need to populate a profile view for a user. We've got some profile details about this user that we want to show, such as the user's name and their avatar. We also have a list of events associated with this user. This could be any sort of comments or poll requests or other type of events that you might seen on GitHub. We also have a list of the users' repositories.
We're keeping the controller lightweight and offloading most of the work to the service. The controller simply calls a method on that service, which returns a promise when then call "then", and we apply the result to the controller.
As we scroll down and take a look at this service, you'll quickly notice that we have what we call a nested promise chain. We use the then method of each promise we get from the HTTP request, and call the next endpoint, all the way down until we finally assemble the expected result.
This gets really hairy really quick. It's ugly, it's hard to read, and the view could really care less about the order in which the data comes back. We want all the data assembled so that we can populate the view. When your code starts to look all deeply nested like this, it's really time to stop and start to think about some alternatives.
The first thing that we need to do is move all these HTTP requests up into their own named functions so that we can call them individually. We'll create a function to fetch the user data first. We're going to move this section up, and then we're going to fix it so that it returns properly and gives us the promise that we expect. Now we'll do the same for the call that fetches our repositories, and finally we'll do it for events.
Now that we've moved all this work up into the named functions, we can delete the body of get angular info and start taking a look at $q to wrangle the mess of promises.
$q is an angular service for dealing with promises. We need to inject into our service so we have access to it, and then we're going to use its hand $q.all method. What $q.all does is it takes an array of promises as an argument, and when every one of the promises has resolved, the $q.all promise resolves.
Now we create three local variables for each of the three promises that we need to fetch. First we'll create a variable for get user data, get events, and get repositories. Each of these will be assigned to the function call, which will return a promise. Now we put these three promises in the array argument for $q.all. Now we call the then method on the $q.all call, and pass it a function that has a result array argument. We also need to make sure that get angular info returns the promise from the call to $q.all.
Now we'll add a console log and reload the page, and you'll see that the response we get is an array that contains each of the responses from the three get methods that we called. This module is already starting to look a lot better.
We still need to do a bit of transformation to get the view of the results that it actually expects. The way that $q.all works is, regardless of the order in which the results come in, the array that you're passed back is going to be in the order that you made the requests in the initial $q.all call. This means that the first item in the array is the user data, the second item is the repository's array, and the third item is the events array.
We'll assign each of those to local variables, and now I'll extend user data and attach the events and repositories as properties of the user data. We reload, and we see that the view is populated as expected.
One of the goals that we're trying to achieve here is removing any complexity from the controller that we possibly can. You'll notice that despite the refactoring we haven't even had to look at the controller or change the call it makes to the service. All it needs to know is that when it asks for data, it gets data.
The service itself is much easier to understand and modify. With a few changes to expose the individual requests, you'd also end up with something that is highly testable. Simply take these named functions, we're going to give them method names on the service, and now we can call them all independently and they can be tested in isolation.