Tidy Up Your Angular Controllers with Factories and Services

Brad Barrow

There was a time, around five or six years ago, when jQuery was dominating the client side of the web. It read like plain English, it was easy to install, and the learning curve was flat enough for toddlers to ride their tricycles on it. With that ease of access however, came a slew of problems. jQuery made it easy to hack together something that “worked” but that came at the cost of best practices, maintainability, and scalability.

Then, the framework wars began and soon everyone was clamouring to try the latest and greatest framework that would bring the promised structure and scalability to their app. One of those frameworks is AngularJS. Now, Angular’s learning curve is significantly steeper than jQuery’s but I think it has reached a point where many developers can quite confidently set up a basic application. That said, using a framework doesn’t automatically solve the core problem of application design. It’s still possible to build applications in frameworks like AngularJS, EmberJS or React that aren’t maintanable or scalable – in fact it’s rather common for beginners and even intermediate framework users to make this mistake.

How Do Things Get Out of Hand So Easily?

In order to demonstrate how this sudden complexity can occur in even the most basic of AngularJS apps, let’s start building one and observe where we might go wrong. Then, later, we’ll look at ways to fix it.

Let’s Create a Simple App

The app we’re going to create is a scoring app for Dribbble players. We’ll be able to type in a Dribbble user’s name and have them added to a score board.

Spoiler – You can see a working implementation of the final product here.

Begin by creating an index.html file with the following contents to get started:

<!DOCTYPE html>
<html>
  <head>
    <title>Angular Refactoring</title>
    <link href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.15/angular.min.js"></script>
  </head>
  <body>
    <div>
      <div class="panel panel-default">
        <div class="panel-heading">Dribbble Player Scores</div>
        <div class="panel-body">
          <p>Add Dribbble players to see how they rank:</p>
          <div class="form-inline">
            <input class="form-control" type="text" />
            <button class="btn btn-default">Add</button>
          </div>
        </div>
        <ul class="list-group">
          ...
        </ul>
      </div>
    </div>
  </body>
</html>

Create Our AngularJS App

If you’ve written an Angular app before, the next few steps should be fairly familiar to you. First of all, we’ll create an app.js file where we’ll instantiate our AngularJS app:

var app = angular.module("dribbbleScorer", []);

Now we’ll include that in our index.html file. We’ll also add the ng-app="dribbbleScorer" attribute to our <html> tag to bootstrap the Angular app.

<html ng-app="dribbbleScorer">
  <head>
    <title>Angular Refactoring</title>
    <link href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.15/angular.min.js"></script>
    <script src="app.js"></script>
  </head>
  ...

Now that our app is setup and bootstrapped we can start handling the business logic of our app.

Making It Work

It’s time to actually implement our app. Remember that we’re approaching this in a “let’s get it working” fashion, because that’s often the reality we’re faced with. In the same way that one might’ve rushed to add a click handler with jQuery, Angular users will often reach for the quickest route to a working app: ng-controller. Let’s see how that might work.

In app.js we’ll define a controller and some dummy player data:

var app = angular.module("dribbbleScorer", []);

app.controller("DribbbleController", function($scope) {
  $scope.players = ["Tom", "Dick", "Harry"];
});

In index.html we’ll insert our controller using ng-controller, and we’ll edit our ul list to loop over the players and display each of them in an li:

<body>
  <!-- Add our DribbbleController -->
  <div ng-controller="DribbbleController">
    ...
    <ul class="list-group">
      <!-- Loop over players using ng-repeat -->
      <li class="list-group-item" ng-repeat="player in players">
        {{player}}
      </li>
    </ul>
    ...
  </div>
</body>

If you save both files and open up index.html in a browser you should see a list of the three names Tom, Dick and Harry. Pretty easy and so far, quite clean.

Implementing the Form

Next, let’s get our form working. We’ll need a variable to use as the ng-model for the input field, and we’ll need a click handler for the button. The click handler will need to add our input to the current list of players.

In index.html add the model and click handler to our form:

<div ng-controller="DribbbleController">
  ...
  <div class="form-inline">
    <input class="form-control" type="text" ng-model="newPlayer" />
    <button class="btn btn-default" ng-click="addPlayer(newPlayer)">Add</button>
  </div>
  ...
</div>

Next, we’ll implement those two things in app.js:

app.controller("DribbbleController", function($scope) {
  $scope.newPlayer = null; // Our model value is null by default
  $scope.players = ["Tom", "Dick", "Harry"];
  
  // Adds a player to the list of players
  $scope.addPlayer = function(player) {
    $scope.players.push(player); 
  }
});

Test it out in the browser. Type in a name, click the Add button, and it should appear in the list. It’s pretty easy to get something working really quickly with AngularJS controllers.

Fetching Data From Dribbble

Now, rather than just using dummy player names, let’s actually fetch the player information from Dribbble. We’ll update our addPlayer() function to send the player name to Dribbble’s API, and push the result into the list instead:

app.controller("DribbbleController", function($scope, $http) {
  $scope.newPlayer = null; // Our model value is null by default
  $scope.players = ["Tom", "Dick", "Harry"];
  
  // Fetches a Dribbble player and adds them to the list
  $scope.addPlayer = function(player) {
    $http.jsonp(
      'http://api.dribbble.com/players/' + player + '?callback=JSON_CALLBACK'
    ).success(function(dribbble_player){
      $scope.players.push(dribbble_player.name);
    }).error(function(){
      // handle errors
    }); 
  }
});

Remember to inject the $http service into your controller first. The Dribbble API is JSONP based, so we need to use the $http.jsonp() method and add ?callback=JSON_CALLBACK to the URL to allow Angular to automagically handle the response for us. The rest is pretty simple. In our success callback, we push the player’s name into the list. Go ahead and try this out in the browser.

Removing a Player

Let’s add a remove button to our player rows. First, make the following changes to index.html.

<ul class="list-group">
  <!-- Loop over players using ng-repeat -->
  <li class="list-group-item" ng-repeat="player in players">
    {{player}}
    <a href="" ng-click="removePlayer(player)">
      <i class="glyphicon glyphicon-remove pull-right"></i>
    </a>
  </li>
</ul>

Then, make these changes in app.js:

app.controller("DribbbleController", function($scope, $http) {
  ...
  $scope.removePlayer = function(player) {
    $scope.players.splice($scope.players.indexOf(player), 1);
  };
});

You should now be able to add and remove players from your list.

Using the player Object

It’s time to create the last bit of our app before we start refactoring. We’re going to create an arbitrary “comment score” and “like score” for our players. But first, we need to turn our player strings into objects so that they can have properties, which we can then display in the DOM. Let’s update app.js to use the actual player objects returned from Dribbble:

app.controller("DribbbleController", function($scope, $http) {
  $scope.newPlayer = null; // Our model value is null by default
  $scope.players = []; // We'll start with an empty list
  
  // Fetches a Dribbble player and adds them to the list
  $scope.addPlayer = function(player) {
    $http.jsonp(
      'http://api.dribbble.com/players/' + player + '?callback=JSON_CALLBACK'
    ).success(function(dribbble_player){
      $scope.players.push(dribbble_player); // Here we add the dribbble_player object to the list
    }).error(function(){
      // handle errors
    }); 
  };
});

Next, let’s update the DOM to use the player’s properties:

<ul class="list-group">
  <!-- Loop over players using ng-repeat -->
  <li class="list-group-item" ng-repeat="player in players">
    <!-- We use player.name here instead of just player -->
    {{player.name}}
    <a href="" ng-click="removePlayer(player)">
      <i class="glyphicon glyphicon-remove pull-right"></i>
    </a>
  </li>
</ul>

The app should still function as normal at this point.

Calculating Scores

Let’s add the score information to the DOM, then we’ll implement it in our JavaScript file:

<ul class="list-group">
  <li class="list-group-item" ng-repeat="player in players">
    {{player.name}} L: {{likeScore(player)}} C:{{commentScore(player)}}
    <a href="" ng-click="removePlayer(player)">
      <i class="glyphicon glyphicon-remove pull-right"></i>
    </a>
  </li>
</ul>

We’ll calculate the scores arbitrarily by subtracting the players given comments from their received comments count, and likewise (excuse the pun) for their given likes and received likes count. We’ll implement that as follows:

app.controller("DribbbleController", function($scope, $http){
  ...
  
  $scope.likeScore = function(player) {
    return player.likes_received_count - player.likes_count;
  };

  $scope.commentScore = function(player) {
    return player.comments_received_count - player.comments_count;
  };
});

Reload the page, add a few players, and you should see a Like (L) score and Comment (C) score for each player.

Look at That Controller!

Now, it’s all well and good that our app is working, but just look at the size and complexity of the controller we created! In an ideal world, a controller should only concern itself with just that: controlling communication between different parts of your app. Here, our controller is responsible for absolutely everything.

app.controller("DribbbleController", function($scope, $http) {
  $scope.newPlayer = null; // Our model value is null by default
  $scope.players = []; // We'll start with an empty list
  
  // Fetches a Dribbble player and adds them to the list
  $scope.addPlayer = function(player) {
    $http.jsonp(
      'http://api.dribbble.com/players/' + player + '?callback=JSON_CALLBACK'
    ).success(function(dribbble_player) {
      $scope.players.push(dribbble_player); // Here we add the dribbble_player object to the list
    }).error(function() {
      // handle errors
    }); 
  };

  $scope.removePlayer = function(player) {
    $scope.players.splice($scope.players.indexOf(player), 1);
  };

  $scope.likeScore = function(player) {
    return player.likes_received_count - player.likes_count;
  };

  $scope.commentScore = function(player) {
    return player.comments_received_count - player.comments_count;
  };
});

We can do better than this.

Using an Angular Factory to Abstract Our Concerns

Adding and removing a player are two concepts that sort of belong in the controller. It’s not so much the fact that the controller exposes these functions, it’s that it’s also responsible for their implementation. Wouldn’t it be nicer if the controller’s addPlayer() function just handed off that request to another part of the app that could handle the ins and outs of actually adding the player. Well, that’s where AngularJS factories come into the picture.

Creating Our factory

If we think in object oriented terms, we’re dealing with a Dribbble player object. So, let’s create a factory that can manufacture Dribbble players. We’ll just implement this in the same app.js file for the sake of ease:

app.controller("DribbbleController", function($scope, $http) {
  ...
});

app.factory("DribbblePlayer", function() {
  // Define the DribbblePlayer function
  var DribbblePlayer = function(player) {
  };

  // Return a reference to the function
  return (DribbblePlayer);
});

You’ll notice that we’ve defined DribbblePlayer with a capitalized syntax. This is because it’s a constructor function. Also note that the constructor function takes a player parameter. When we inject this factory into our controller, we’ll be able to call new DribbblePlayer(player) and have it return a constructed instance of itself configured to that player.

Let’s add an initialize function to the DribbblePlayer constructor to set some default properties:

// We need to inject the $http service in to our factory
app.factory("DribbblePlayer",function($http) {
  // Define the DribbblePlayer function
  var DribbblePlayer = function(player) {
    // Define the initialize function
    this.initialize = function() {
      // Fetch the player from Dribbble
      var url = 'http://api.dribbble.com/players/' + player + '?callback=JSON_CALLBACK';
      var playerData = $http.jsonp(url);
      var self = this;
      
      // When our $http promise resolves
      // Use angular.extend to extend 'this'
      // with the properties of the response
      playerData.then(function(response) {
        angular.extend(self, response.data);  
      });
    };

    // Call the initialize function for every new instance
    this.initialize();
  };

  // Return a reference to the function
  return (DribbblePlayer);
});

There’s a few things to take notice of here:

We define the self variable as a reference to this wich in that context is the constructed DribbblePlayer instance. We do this so that the instance is available to be extended inside the promise’s then() callback.

We also use angular.extend() to add all of the Dribbble players properties which we got back from the API to our DribbblePlayer instance. This is the equivalent of doing:

playerData.then(function(response) {
  self.name = response.data.name;
  self.likes_count = response.data.likes_count;
  // etc
});

We invoke this.initialize() immediately after defining it. This is to simulate normal OOP behavior where defining a constructor or initialize() method will cause that method to be executed when a new instance of that class is created.

Using the Factory

It’s time to use our factory. We’ll need to inject it into our controller, and then we can use it to abstract some of the responsiblity from the controller:

...

// Inject DribbblePlayer into your controller and remove the $http service
app.controller("DribbbleController", function($scope, DribbblePlayer) {
  $scope.newPlayer = null;
  $scope.players = [];

  $scope.addPlayer = function(player) {
    // We can push a new DribbblePlayer instance into the list
    $scope.players.push(new DribbblePlayer(player));
    $scope.newPlayer = null;
  };
  ...
});

Reload the app in your browser and it should work just as it did before. Isn’t that awesome?

What Exactly is Going on Here?

To recap, we’ve injected our DribbblePlayer factory into our controller. The factory allows us to create new instances of the DribbblePlayer constructor function. The contructor’s initialize() method uses the player name parameter to fetch the player details from Dribbble and set them as properties on the instance. Lastly, that instance is what we push into our list.

We don’t need to change the DOM at all because it’s expecting objects that have a name and like_count, and that’s exactly what we’re giving it.

Was That Really Worth It?

Absolutely! Not only have we simplified our controller, we’ve separated our concerns. Our controller is no longer concerned with the implementation of adding a player. We could swap out new DribbblePlayer() for new BaseballSuperstar(), and we’d only need to change one line of code. Furthermore, we can abstract other parts of the controller now too, using a more readable and scalable OOP approach.

Let’s move the likeScore() and commentScore() into our factory and set them as methods on each player instance rather than functions that take a player parameter:

  ...

  this.initialize = function(argument) {
    ...
  };

  this.likeScore = function() {
    return this.likes_received_count - this.likes_count;
  };

  this.commentScore = function() {
    return this.comments_received_count - this.comments_count;
  };
}

Now, each time we call new DribbblePlayer(player) the object we get back will have a likeScore() method and a commentScore() method. They need to remain as functions rather than properties such that on each of Angular’s $digest cycles they will generate new values to represent any potential changes in the DribbblePlayer model.

We’ll need to update our DOM to reflect these changes:

<ul class="list-group">
  <li class="list-group-item" ng-repeat="player in players">
    <!-- We can now use player.likeScore instead of likeScore(player) -->
    {{player.name}} L: {{player.likeScore()}} C:{{player.commentScore()}}
    <a href="" ng-click="removePlayer(player)">
      <i class="glyphicon glyphicon-remove pull-right"></i>
    </a>
  </li>
</ul>

Wrapping Up

I’ve tried to demonstrate just how easy it is for us to write code that just “gets it working” and for that code to very quickly get out of hand. We ended up with a messy controller, full of functions and responsibilities. However, after some refactoring, our controller file now looks like this:

app.controller("DribbbleController", function($scope, DribbblePlayer) {
  $scope.newPlayer = null;
  $scope.players = [];

  $scope.addPlayer = function(player) {
    $scope.players.push(new DribbblePlayer(player));
  };

  $scope.removePlayer = function(player) {
    $scope.players.splice($scope.players.indexOf(player), 1);
  };
});

It’s far more readable and concerns itself with very little – and that’s what refactoring is all about. I hope that I’ve provided you with the tools you need to start considering better approaches to structuring your AngularJS applications. Happy refactoring!

The code from this tutorial is available on GitHub!

Extra Credit

We certainly improved the addPlayer() function, but why stop there? Here are a couple of other refinements we could make:

  • Abstract the $http call out into an Angular resource to decouple persistence/resourcing. You could then inject the resource into your factory in order to use it.
  • Create a PlayerList factory to handle list management including adding, removing, and sorting. This way you could abstract the push() and splice() methods behind PlayerList.add() and PlayerList.remove() so that you’re not reliant on that implementation directly inside your controller.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://mikefisher.io Mike Fisher

    Great post Brad! This is exactly what I’m dealing with in my Angular app at the moment. Does anyone know of anymore resources on this topic that I could dive in a little deeper with?

  • Brad Barrow

    Thanks Mike Fisher :) I’d say Google is your friend here… some key phrases would be OOP AngularJS or Angular JS classes because essentially that’s what this is encouraging.

    Furthermore, if it’s an option I’d strongly recommend Coffeescript as your language of choice especially when adopting this pattern. Coffeescript does a much better (readable) job of handling class/oop structures in JS.

    Last but not least, Ben Nadel wrote a similar post here: http://www.bennadel.com/blog/2527-defining-instantiatable-classes-in-the-angularjs-dependency-injection-framework.htm

    and (for the next time you start up a new Angular project) you might like to look at Angular Classy https://github.com/davej/angular-classy

  • http://www.arttechint.com/ Suraj Rai

    Hi Brad Barrow,
    You really shared some critical code about angular controllers that were difficult to discuss and find. It helped me to increase my knowledge angular controller. Your post is really nice and helpful to everyone. Thanks to share such useful post.