Scalable code organization in AngularJS

Implement a modular application architecture

Gert Hengeveld
Nov 20, 2013 · 6 min read

This is the first in a series of articles about the best practices I’ve learned to apply when working on single-page applications with AngularJS. It started out as a basic list of best practices for some colleagues who were just getting to know the framework. This series will provide in-depth information and arguments supporting my tips. As the title of the series suggests, this stuff is based on opinions, both other developers’ and my own. I may be wrong, others may be wrong. Suggestions for improvement are welcome.

Organizing your code

A lot of the structure of your application is defined by the way your files are organized. AngularJS doesn’t prescribe a certain directory structure, but there are three commonly used patterns.

The file pattern: a file for each type of object

This approach is the one you too often find in sample projects, plunks and beginner tutorials. It was popularized by the angular-seed project and basically means you have no structure whatsoever. Here’s what it looks like:

app
assets
(images etc.)
partials
(directive and ng-include templates)
scripts
app.js
controllers.js
directives.js
filters.js
services.js
styles
(sass/less/css files)
tests
config
e2e
unit
views
(route templates)
index.html

The problem with this approach is that it simply doesn’t scale. As your project grows you quickly end up with enormous files and you find yourself scrolling up and down a lot to find what you’re looking for. You’ll quickly start to look for an alternative which you’ll find in the folders pattern.

The folders pattern: a directory for each type of object

The file pattern naturally evolves into separate files grouped together in directories, still following the same grouping. Instead of having a file for each object type, you have a directory for each of these types:

scripts
controllers
login-controller.js
directives
dropdown-menu.js
filters
fancy-value.js
services
auth-service.js
app.js

This approach will scale a lot better and will actually work quite well if you have no more than a dozen files in any of those directories. You may even get away with it for larger projects by using subdirectories to limit the number of files in one directory. Still, it does nothing to help you find a certain piece of code if you don’t know where to look and it certainly doesn’t indicate any relationship between files.

The module pattern: a module for each feature

Instead of grouping files by their type and kind (e.g. controllers, directives, services), the recommendation is to group files by feature. If you’re starting a new project, I recommend using ng-boilerplate as a starting point. Features are the “high-level sections of the application, often corresponding to top-level routes”. Each feature gets its own directory containing all of the logic, routes, templates, styles and tests directly related to the feature. The feature also gets its own module definition, similar to the one in app.js, so it can have its own configuration. Using this structure has several advantages:

Here’s an example of such a structure in action:

app
about
about.html
about.js
about.spec.js
home
home.html
home.js
home.spec.js
app.js
app.spec.js
assets
(images etc.)
common
directives
  resources (or models)
  services
styles
(sass/less/css files)
index.html

The most important change is the introduction of a module file for each feature: about.js and home.js. They are similar to app.js in that they define a new Angular module. A big change is that this will also be the location of any route related to the feature, meaning your route definitions will no longer be in one place, preventing app.js from becoming unmanageably large. It’s a good practice to use dot notation in the module name as a namespace and to indicate a relationship (but not a dependency):

angular.module('myApp.home', [ ]).config(function ($routeProvider) {
$routeProvider.when('/home', {
templateUrl: 'home/home.html',
controller: 'HomeController',
resolve: {
newsItems: function ($http) {
return $http.get('api/news');
}
}
});
})
.controller('HomeController', function ($scope, newsItems) {
$scope.newsItems = newsItems;
});

A good reference application following this structure is angular-app. One of the things I don’t feel good about is that the controllers are included in the same file as the module definition. Controllers can get quite large and so can routes if you have a lot of stuff to resolve. I suppose you have to decide for yourself if that’s acceptable. Otherwise you may choose to put controllers in their own file, perhaps even grouped together in a controllers directory.

If you look at angular-app’s common directory, you’ll notice it’s a hybrid of the folders pattern and the module pattern. The directives, resources (RESTful models) and services directories are simply collections of stand-alone objects, while security is a module. My suggestion is to put all your modules in the app directory and avoid creating this hybrid.

Another change worth mentioning is the location of the unit tests. In the example above you’ll see the tests (*.spec.js) are located alongside the script they belong to instead of in a separate tests directory. Having your tests in the same directory as their counterparts means they are easy to locate and promotes that they are updated as the script to be tested changes. However it will be a little more difficult to setup your testing tool (Karma) and build tool (Grunt) so tests will be run but not included in the build. In the end I think it comes down to personal preference as both approaches have their strengths and weaknesses.

Migrating to the modular architecture

Many projects start out with the folders pattern but end up being more suited for the modular architecture. If you find yourself wanting to migrate from the folders pattern to the module pattern, here’s some advice.

Next up in the series: Advanced routing

Opinionated AngularJS

Practical tips and best practices for developing AngularJS…

Opinionated AngularJS

Practical tips and best practices for developing AngularJS applications

Gert Hengeveld

Written by

Enabling Component-Driven Development with @ChromaUI and @Storybookjs.

Opinionated AngularJS

Practical tips and best practices for developing AngularJS applications