Code Organization in Large AngularJS and JavaScript Applications
Many developers struggle with how to organize an application's code
base once it grows in size. I've seen this recently in AngularJS and
JavaScript applications but historically it's been a problem across
all technologies including many Java and Flex apps I've worked on in
the past.
The general trend is an obsession with organizing things by type. It
bears a striking resemblance to the way people organize their
clothing.
Piles on the Floor
Let's take a look at angular-seed, the official starting point for
AngularJS apps. The "app" directory contains the following structure:
css/ img/ js/ app.js controllers.js directives.js filters.js
services.js lib/ partials/ The JavaScript directory has one file for
every type of object we write. This is much like organizing your
clothes into different piles on the floor. You have a pile of socks,
underwear, shirts, pants, etc. You know your black wool socks are in
that pile in the corner but it's going to take a while to dig them
out.
This is a mess. People shouldn't live like this and developers
shouldn't code like this. Once you get beyond a half-dozen or so
controllers or services these files become unwieldy: objects you're
looking for are hard to find, file changesets in source control become
opaque, etc.
The Sock Drawer
The next logical pass at organizing JavaScript involves creating a
directory for some of the archetypes and splitting objects into their
own files. To continue the clothing metaphor, we've now invested in a
nice mohaghony dresser and plan to put socks in one drawer, underwear
in another, and neatly fold our pants and shirts in still others.
Let's imagine we're building a simple e-commerce site with a login
flow, product catalog and shopping cart UI's. We've also defined new
archetypes for Models (business logic and state) and Services (proxies
to HTTP/JSON endpoints) rather than lumping them into Angular's single
"service" archetype. Our JavaScript directory can now look like this:
controllers/ LoginController.js RegistrationController.js
ProductDetailController.js SearchResultsController.js directives.js
filters.js models/ CartModel.js ProductModel.js SearchResultsModel.js
UserModel.js services/ CartService.js UserService.js ProductService.js
Nice! Objects can now be located easily by browsing the file tree or
using IDE shortcuts, changesets in source control now clearly indicate
what was modified, etc. This is a major improvement but still suffers
from some limitations.
Imagine you're at the office and realize you need a few outfits
dry-cleaned for a business trip tomorrow morning. You call home and
ask your significant other to take your black charcoal and blue
pinstripe suits to the cleaners. And don't forget the grey shirt with
the black paisley tie and the white shirt with the solid yellow tie.
Imagine that your significant other is completely unfamiliar with the
your dresser and wardrobe. As they sift through your tie drawer they
see three yellow ties. Which one to pick?
Wouldn't it be nice if your clothing was organized by outfit? While
there are practical constraints like cost and space that make this
difficult with clothing in the real world, something similar can be
done with code at zero cost.
Modularity
Hopefully the trite metaphors haven't been too tedious but here's the
recap:
Your significant other is the new developer on the team who's been
asked to fix a bug on one of the many screens in your app. The
developer sifts through the directory structure and sees all the
controllers, models and services neatly organized. Unfortunately it
tells him/her nothing about which objects are related or have
dependencies on one another. If at some point the developer wants to
reuse some of the code, they need to collect files from a bunch of
different folders and will invariably forget code from another folder
somewhere else. Believe it or not, you rarely have a need to reuse all
of the controllers from the e-commerce app in the new reporting app
you're building. You may however have a need to reuse some of the
authentication logic. Wouldn't it be nice if that was all in one
place? Let's reorganize the app based on functional areas:
cart/ CartModel.js CartService.js common/ directives.js filters.js
product/ search/ SearchResultsController.js SearchResultsModel.js
ProductDetailController.js ProductModel.js ProductService.js user/
LoginController.js RegistrationController.js UserModel.js
UserService.js Any random developer can now open the top-level folder
and immediately gain insight into what the application does. Objects
in the same folder have a relationship and some will have dependencies
on others. Understanding how the login and registration process work
is as easy as browsing the files in that folder. Primitive reuse via
copy/paste can at least be accomplished by copying the folder into
another project.
With AngularJS we can take this a step further and create a module of
this related code:
1 2 3 4 5 6 7 8 9 10 11 12 13 var userModule =
angular.module('userModule',[]); userModule.factory('userService',
['$http', function($http) { return new UserService($http); }]);
userModule.factory('userModel', ['userService', function(userService)
{ return new UserModel(userService); }]);
userModule.controller('loginController', ['$scope', 'userModel',
LoginController]); userModule.controller('registrationController',
['$scope', 'userModel', RegistrationController]); view
rawUserModule.js hosted with ❤ by GitHub If we then place
UserModule.js into the user folder it becomes a "manifest" of the
objects used in that module. This would also be a reasonable place to
add some loader directives for RequireJS or Browserify.
Tips for Common Code
Every application has common code that is used by many modules. We
just need a place for it which can be a folder named "common" or
"shared" or whatever you like. In really big applications there tends
to be a lot of overlap of functionality and cross-cutting concerns.
This can be made manageable through a few techniques:
If your module's objects require direct access to several "common"
objects, write one or more Facades for them. This can help reduce the
number of collaborators for each object since having too many
collaborators is typically a code smell. If your "common" module
becomes large subdivide it into submodules that address a particular
functional area or concern. Ensure your application modules use only
the "common" modules they need. This is a variant of the "Interface
segregation principle" from SOLID. Add utility methods onto $rootScope
so they can be used by child scopes. This can help prevent having to
wire the same dependency (such as "PermissionsModel") into every
controller in the application. Note that this should be done sparingly
to avoid cluttering up the global scope and making dependencies
non-obvious. Use events to decouple two components that don't require
an explicit reference to one another. AngularJS makes this possible
via the $emit, $broadcast and $on methods on the Scope object. A
controller can fire an event to perform some action and then receive a
notification that the action completed. Quick Note on Assets and Tests
I think there's more room for flexibility with respect to organizing
HTML, CSS and images. Placing them in an "assets" subfolder of the
module probably strikes the best balance between encapsulating the
module's asset dependencies and not cluttering things up too much.
However I think a separate top-level folder for this content which
contains a folder structure that mirrors the app's package structure
is reasonable too. I think it works well for tests as well.