A Primer for $http in AngularJS

While working on my latest application, Schedulize, I ran into the problem that many programmers working with Angular encounter: I had poorly written controllers. It's true. My controllers did not look great. They had become cluttered and unmanageable, and my tendency to put global logic in them made it difficult for me to transition information between different parts of my application.

Naturally, putting all of this logic in my controllers was a bad idea and against Angular best practices, so I decided to take a crack at refactoring my http requests into a single factory.

The Problem in Detail

I needed to pull schedule data from my server to display schedules to the user. I had set up an API endpoint at /api/schedules that could accept GET requests and return all schedules in the database(This will be narrowed down to company specific schedules later).

I needed this schedule data to display schedules on the screen, so initially my controller looked something like this:

angular.module('myApp')  
.controller('scheduleViewCtrl', function($scope, $http){
    // on initialization, load all schedules
    $http.get('/api/schedules')
        .success(function(data, status){
              console.log("Retrieved Schedules.", status);
            $scope.schedules = data;
        }
}); 

Naturally, this is a simplified version of my massive controllers, but I ran into a problem when multiple controllers needed access to the same schedule data.

Assuming I had another controller elsewhere that managed a company dashboard page, I would have two options to access the schedule data. The first would involve navigating the scope inheritance, which would look something like $scope.schedules = $scope.$parent.$parent.schedules;. That's definitely not a good approach.

The other option would be to simply repeat my last code block and do a separate GET request to the server. An EVEN WORSE approach.

So, essentially, doing get requests in individual controllers has a few issues:

  1. It clutters the code in your controllers and makes it hard to separate concerns.
  2. It is typically an antipattern to put global data stores in your controller.
  3. It forces you to either use a confusing method to access data or repeat GET requests, both bad ideas.

So, what is the solution? Put all this logic and the Single Source of truth for our data in our factories/services. I'll outline that approach here.

The Solution

Instead of placing our logic in the controller, we can move that logic to functions within a controller and use promises to make sure we only send a SINGLE GET REQUEST to the server per page load. The approach to solve this problem mirrors, in many ways, the singleton design pattern in Java.

Essentially, we will create a variable to store the data we wish to fetch. Once we have fetched data once, we check the variable to see if it has data. If it does, we know to not fetch the same data again. We can also create functions to modify this data and push it all to the server at once(i.e. with a save button).

The actual solution also draws upon promises using angular's built in $q module to maintain the asynchronicity of the $http requests. Here is the code:

angular.module('myApp.services')  
.factory('Schedule', function($http, $q){
  // For storage
  var schedules = null;

  return {
    getAllSchedules: function(){
      // Create a promise to be returned
      var deferred = $q.defer();
      // If we have not yet fetched all our schedules...
      if(!schedules) {
        // Request has not been made, so make it
        $http.get('/api/schedules').then(function(res) {
          //just a bit of date formatting using Moment.js
          for(var i = 0; i < res.data.length; i++){
            res.data[i].createdAt = moment(res.data[i].createdAt).format('MMMM Do, YYYY');
          }
          deferred.resolve(res.data);
        });
          // save the promise to schedules
          schedules = deferred.promise;
        }
        // Return the stored promise with the data
        return schedules;
    }
  };
});

As you can see, the code returns a promise, so we don't have to worry about when the GET request is actually being done. It will do it once, but it will return a promise every time. That means, any time we want to use our function, we can just drop in some simple code into our controller. Here is the same controller as above, refactored to use our factory:

angular.module('myApp')  
.controller('scheduleViewCtrl', function($scope, Schedule){
    // on initialization, load all schedules
    Schedule.getAllSchedules()
        .then(function(schedules){
            $scope.schedules = schedules;
        };
    // So simple!
}); 

It may seem like a small change, but this code is now way more readable, and it preserves the promise-nature of the $http requests. In general, this is a good approach for managing logic in an angular application.

If anything doesn't make sense or you have any questions, feel free to comment, and I can hopefully clear up your issues!

comments powered by Disqus