Structuring AngularJS Code
There are many different ways to define AngularJS controllers, factories, services, filters, and directives. For example, some devs start out by defining controllers using functions which gets the job done:
function MainController($scope) { //Code goes here }
Although this code works, the function is in the global scope and the $scope parameter name could get mangled during script minification. As a result, a lot of people change it to the following to be more aligned with the “Angular way”:
angular.module('moduleName').controller('MainController', ['$scope', function($scope) { //Code goes here }]);
While this also gets the job done, I’ve always found it to be a bit messy. I’d rather focus on the JavaScript code first, get it working properly, and then plug it into the Angular machinery. Here’s the general pattern I like to follow for controllers, factories, services, filters, and directives.
- Wrap everything with an immediately invoked function to pull code out of the global scope
- Define the controller/factory/service using standard JavaScript
- Plug the custom JavaScript code for the controller/factory/service/filter/directive into AngularJS
- Optional: Inject the parameter names to avoid minification problems using the $inject property. I’ve always done this when actually defining the controller/factory/service/filter but decided that using $inject is a little cleaner (credit to Scott Allen for a discussion we had on $inject that convinced me it was the way to go).
Note: Check out ng-annotate if you don’t want to worry about injecting the parameter names to avoid script minification issues. It’ll handle doing that after the fact. Thanks to Mike Alsup for mentioning it. He also mentioned an interesting alternative when you want to keep the injection of parameter names immediately next to where they’re used in a function that I mention below.
Here’s what this pattern looks like for controllers, factories, services, filters, and directives. Keep in mind there isn’t “one way” to write this code. What follows is my current preference.
Controllers
(function () { var ControllerName = function ($scope) { }; ControllerName.$inject = ['$scope']; angular.module('moduleName').controller('ControllerName', ControllerName); }());
If you want to keep the injection of parameter names close to the function definition the following will work (hat tip to Mike Alsup for the idea). I don’t use this approach currently, but it’s another option and the same general pattern could be followed for the code samples that follow:
(function () { ControllerName.$inject = ['$scope']; function ControllerName($scope) { } angular.module('moduleName').controller('ControllerName', ControllerName); }());
Factories
(function () { var factoryName = function ($http) { var factory = {}; return factory; }; factoryName.$inject = ['$http']; angular.module('moduleName').factory('factoryName', factoryName); }());
Services
(function () { var serviceName = function ($http) { }; serviceName.$inject = ['$http']; angular.module('moduleName').service('serviceName', serviceName); }());
Filters
(function () { var filterName = function () { return function (items, filterValue) { if (!filterValue) return items; var matches = []; filterValue = filterValue.toLowerCase(); for (var i = 0; i < items.length; i++) { var item = items[i]; //custom filter logic here } return matches; }; }; angular.module('moduleName').filter('filterName', filterName); }());
Directives
I start out with the following for directives to help me remember the main pieces that are available to use. The “wc” prefix is something I add for my company – Wahlin Consulting.
(function () { var controller = function ($scope) { $scope.myVal = $scope.title; }; controller.$inject = ['$scope']; var wcDirectiveName = function () { return { restrict: 'A', //E = element, A = attribute, C = class, M = comment scope: { title: '@' //@ reads attribute value, = provides two-way binding, & works w/ functions }, template: '<div>{{ myVal }}</div>', controller: controller, link: function ($scope, element, attrs) {
} } }; angular.module('moduleName').directive('wcDirectiveName', wcDirectiveName); }());
Keep in mind that using $inject is optional as mentioned earlier. I’m fine with injecting the parameters the following way as well:
(function () { var ControllerName = function ($scope) { }; angular.module('moduleName').controller('ControllerName', ['$scope', ControllerName]); }());
The reason I like $inject more is that it keeps the injected parameter names close to the actual function that uses the parameters which makes it easier to keep things matched up properly. It’s simply a personal choice though and only something I’ve switched to using recently.
One thing that I don’t like is duplicating the module name over and over across controllers, factories, etc. especially since it’s a string. While I try to avoid global variables, I’ve defined the module name using a variable in apps (as a global variable – I treat it like a “constant”) that has a specific name that’s unlikely to be duplicated and then reference it in angular.module() to avoid typos in strings. Ultimately it doesn’t really matter and it’s important to keep in mind that there’s no one “right” way to do this. Anyone who tells you otherwise either doesn’t know what they’re talking about or is too high and mighty to consider alternatives. :-)
Finally, some people like to declare a global variable that represents the angular.module object and then reference it when defining controllers, etc. As long as the variable name is descriptive and not likely to be stepped on that works as well (this topic just came up today in a discussion so here’s an example of what I mean):
var yourRootNamespace = yourRootNamespace || {}; yourRootNamespace.yourApp = angular.module('moduleName', ['ngRoute']); //Can now get to main app anywhere using namespace yourRootNamespace.yourApp.controller('ControllerName', ControllerName);
I like to lookup the module dynamically as shown in the earlier samples and avoid as many global variables as possible. It all comes down to personal preference and how you like to write your code though.
Conclusion
I’ve never believed that “one size fits all” and typically evolve my code as I spend more and more time with a given framework. For now, this is the pattern I like to follow though when working with AngularJS controllers, factories, services, filters, and directives. Feel free to adjust it based on your individual likes/dislikes.