-
Menu
CLIFF MEYERS
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.
http://cliffmeyers.com/http://cliffmeyers.com/blog/2013/4/21/code-organization-angularjs-javascripthttps://github.com/angular/angular-seed
-
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
-
view raw
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:
UserModule.js hosted with by GitHub
123456789
10111213
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]);
12
var userModule = angular.module('userModule',[]);
https://gist.github.com/cliffmeyers/3d1db5373313938c3b87/raw/UserModule.jshttps://gist.github.com/cliffmeyers/3d1db5373313938c3b87#file-usermodule-jshttps://github.com/
-
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:
1. 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.
2. 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.
3. 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.
4. 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.
3456789
10111213
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]);
https://gist.github.com/cliffmeyers/3d1db5373313938c3b87/raw/UserModule.jshttps://gist.github.com/cliffmeyers/3d1db5373313938c3b87#file-usermodule-jshttps://github.com/
-
Newer Older
Comments (34) Subscribe via e-mail
Great post, I wonder if you could add some more gists to show
how you define your controllers or
services. Just for all of us that dont know yet all the possible
ways to declare a service/controller
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.
! "
100 Likes
$ Share
$ Share
Oldest First
Preview Post Comment
raul Arabaolaza A year ago
http://cliffmeyers.com/blog/2013/8/27/mocking-server-dependencies-in-javascript-and-angularjs-applicationshttp://cliffmeyers.com/blog/2013/3/11/integration-angularjs
-
and add it to a module
I think angular-app is a good example of the style:
https://github.com/angular-app/angular-app
Take a look at ng-boilerplate
http://joshdmiller.github.io/ng-boilerplate/#/home by Josh David
Miller
a active member of Angular community.
Completely agree! I've been exploring this kind of organization
as well for my Backbone app (I use
Browserify). I also have a common or core folder with things
shared by all modules.
One of the challenges I still haven't quite solved is the
"assets" bit. I'm trying to keep them separated
in each module (vs a global "assets" folder). I have quite a bit
of shared styles in the common folder,
notably Sass or LESS variables to define colors, type, etc. That
means the styles in each module will
have to remain in Sass or LESS until the whole application is
built.
I'm also exploring making it really easy to work on one piece
(module) at a time, and to do so, being
able to boot the app with only "common" and that particular
module (let's say "cart"), and maybe
"user" (for me it's part of "common"). Right now I'm doing that
at the "debug" build step, where I
have a custom Grunt.js task that allows me to specify a
particular module to only build the app with
that module and it's "common/core" dependencies. If I don't, it
builds the whole app. Have you
considered that? I guess I'm also trying to make it so I could,
if I wanted, put a module in its own
NPM or Bower package.
Thanks for the post!
Cheers,
Nicolas
Phaedryx A year ago
hcentelles A year ago
Nicolas Hery A year ago
-
You've got "return new UserModel" in your userModel factory. I'm
curious to see where UserModel
is defined, and what it looks like.
I usually keep my logic in my controllers, which is probably not
the best way of doing it, and would
like to mimic what you've got going. Would be nice to get
insight into what's behind that UserModel
object.
I actually use a combination of Sock Drawer and Modularity. I
keep files seprated by type but name
the files by modules.
root
--imgs
----idv.TOC
-------image1.png
-------image2.png
--css
----idv.TOC.css
----SiteFeaturesAddressSearch.css
----SiteFeaturesCustomSearches.css
--js
----idv.TOC.js
----SiteFeaturesAddressSearch.js
----SiteFeaturesCustomSearches.js
This works really well for me because when in debug mode each
file for css and js are loaded
separately. Also I can make sure css are loaded at the top of
page and js files are at the bottom.
When the site is set to release mode my minification and merging
code can find the files easily and
stream them as only 2 files.
Aaron Smith A year ago
Donny V A year ago
-
Thanks for this post. I have been working with backbone for a
year or so now but am seriously
thinking it might be time to make the leap to Angular (or even
Meteor) but was a little put off by the
suggested structure from the angular-seed project.
the current structure I have migrated to for my
backbone/marionette projects (w/require.js) is as
follows:
/mod1/
- mod1.controller.js ( similar to the manifest file above)
- mod1.models.js
- mod1.views.js
- mod1.templates.html
/mod2/
- mod2.controller.js
- mod2.models.js
- mod2.views.js
- mod2.templates.html
with a global 'EventBus' used to facilitate communication
between models and the application
'controller'.
So far it's proving to be a pretty solid structure especially in
combination with requirejs but it still
doesn't make up for some of the core problems with backbone in
general, hence the consideration
of Angular as a next step.
so thanks again for posting about an alternative organization
structure that makes a little more
sense to me.
A question I have though is around pub/sub in Angular. I see you
mention it in point #4
(emit/broadcast/on) above but I haven't seen it in any examples
or tutorials yet. are there any good
examples out there you can point to?
Sean
Sean Brookes A year ago
-
Spreading related things into multiple directories isn't really
modularity IMO, pros and cons of each
approach I suppose but I definitely found these rails-style
approches really annoying in the past,
certainly not what I would call modular.
Nice article. I've always tried to structure code after the
domain instead of the techniques used.
What the code does is always more important than HOW it does
it.
@raul @aaron @sean I'll have a follow-up post soon which will
show the tiered architecture we're
using on a large enterprise app plus a few other enhancements I
plan to use on my next app.
@nicolas The bootstrapping of a single module is a great way to
accelerate development. We
actually did some of that back in the day with Flex
applications. If you have nice clean modules and
easily mockable/pluggable dependencies then that bootstrapping
process becomes a lot easier. We
aren't currently using this technique on my current project but
I'd like to get there. Thanks for
sharing!
I would prefer to have folder for directives and filters as
well, with each directive/filters in their own
file.
TJ Holowaychuk A year ago
Thomas Haukland A year ago 1 like
Cliff Meyers A year ago 1 like
Cliff Meyers A year ago
Suman Paul A year ago
-
great tips to organize web apps better, thanks for sharing
I agree too, just seems logical to group this way. Just made
sure that my own little boilerplate for
Angular starts off like this
https://github.com/mattstyles/yeoman-angular-express-plus
This is really nice, and I am interested creating a RequireJS
example of what you have done, if
nobody else has already done so.
Nice article. I'm all for the second organization by
function.
I feel strongly that the rails-style controllers/views/models
separation is a mistake. It took the Rails
Community 5 - 7 years before they stopped writing poorly
structured applications due to the implicit
lack of modularity and abstraction that encouraged. It was funny
watching Rails go from the skinny-
model-era to the skinny-controller-era and now to the
skinny-model and skinny-controller, lets use
services and design patterns era.
IMHO the concept of a component or package MUST be a top-level
abstraction in any sane software
structuring approach. And a directory fills this definition just
fine.
I also feel that AngularJS apps should be written to enable a
"zip and ship" philosophy. i.e. you can
take your app/ directory, zip its contents, and unzip into
/var/www on an Apache, nginx, or Node.js
server and be off and running.
If we follow that strategy we can hopefully hasten the death of
PHP.
JavaScriptBank A year ago
Matt Styles A year ago
Joshua Gough A year ago
Nick Van Weerdenburg A year ago
http://www.javascriptbank.com/
-
@nick how does frontend dev related to PHP? :/
@chris Thanks for the article, I'm always struggling to decide
on the "perfect" folder structure, your
article helped clear up some things I didn't think about.
Could you explain what should be in service, model and
controller? thank in adv
btw, great post
The fonts and colors on this page make it very hard to read!
Interesting. We take a slightly different approach, in our
boilerplate http://brandid.github.io/parse-
angular-demo/
We don't want to load all the controller files on first load.
Could you please throw some light on lazt
loading of controller files only when required. ?
Curious if you've ever seen, or recommend, adding filters to
your individual modular objects. I
definitely can see using a common filters.js, but should you put
all of your filters in there, or should
you move your filters specifically for the users view to
UserFilters.js?
Jason Yang A year ago
Chok Wee Ching A year ago
Nick Perkins A year ago
Arush A year ago
praveen vedanth A year ago
fyjs A year ago
http://fuckyeahjavascript.tumblr.com/
-
I absolutely loved this post. I've set up a structure like this,
and someone pointed me to this article
showing me why I did it. Thank you.
I do, however, have a single question for you.
At the end of "Modularity" you said: "This would also be a
reasonable place to add some loader
directives for RequireJS or Browserify."
Would you mind explaining this a bit more? What's a "loader
directive"? Because I am, in fact, using
browserify.
hi
my structure is below :
for example user section
.
.
---user Folder
------ Controller Folder
----------- LoginController.js
----------- RegistrationController.js
------ Model Folder
----------- UserModel.js
------ Service Folder
----------- UserService.js
.
.
Do you accept this structure?
Wesley Overdijk A year ago
Naser Tahery A year ago
-
Great example! Coming from PHP development, it looks to me more
or less like Symfony2 Bundle
structure idea.
Cheers
I was happy to stumble upon your article and see that you
organized your files in almost the same
way I did. The one exception is that I consider models to be
common. For example you show a
folder for Cart, Product and User, each having their own models,
but Cart would use its own model,
plus the Product model and the User model, which in my mind
makes them common.
Food for thought...
Nice post! I'm new to angularJS and It would be nice if you
provide a project with this strucure. I'm
very confused on how to "connect" separeted controllers in my
app. Thanks!
Hey Cliff,
I had a look at your repo on github and find you need to declare
extra namespaces to store class
definition so later you can instantiate those inside
manifest.
Do you think in the end, manifest file actually cause more
trouble than benefit?
max A year ago
Darryl A year ago
Joao Grassi A year ago
Jon A year ago
http://www.labanalytix.com/
-
Thanks
Great post. I think your clothing analogy works well and flows
with the topic.
Have been doing this for a few years. The same idea but with
some .NET specifics:
http://www.ndepend.com/Res/NDependWhiteBook_Namespace.pdf
Great post. Thanks
Really good post for a newbie like me. Its a good kickstart
point for my new app
Wonderful article to have a good idea about best approach of
working with ng.
Thanks a lot.
Nazmul Hasan
anony 7 months ago
Cody 6 months ago
Roman Boiko 3 months ago
Kaushik 3 months ago
Shinu Suresh 2 months ago
Nazmul Hasan Sarkar 2 months ago
http://www.codyromano.com/
-
Twitter
Linkedin
Google
Facebook
Github
I'm a Solutions Architect for Universal Mind, a digital
solutions agency specializing in applications that deliver a
unified multiscreen customer experience.
AngularJS AOP Books Eclipse Flash Flex Git Hibernate HTML5Java
JavaScript jQuery Localization Mac MustacheOOP Sencha Touch Spring
Subversion Testing Tools
On The Twitter
RT @StoneBrewingCo: BIG THANKS to all who vied for #StoneEast.
We now know our East Coast brewery site.
CHECK IT http://t.co/lFYrVY2eTa #beer #craftbeer #RVA
2 months ago
iOS 8s predictive QuickType keyboard found to suggest parts of
your passwords http://t.co/HxTQvul90p
2 months ago
RT @trochette: Friday was my last day @universalmind. 2 years
working on amazing projects, traveling and
learning. I'll miss you guys!!
2 months ago
Follow Follow @cliffmeyers@cliffmeyers
https://twitter.com/cliffmeyershttp://www.linkedin.com/in/cliffmeyershttps://plus.google.com/116522540335417503947http://www.facebook.com/cliffmeyershttps://github.com/cliffmeyershttp://www.universalmind.com/http://cliffmeyers.com/blog?category=AngularJShttp://cliffmeyers.com/blog?category=AOPhttp://cliffmeyers.com/blog?category=Bookshttp://cliffmeyers.com/blog?category=Eclipsehttp://cliffmeyers.com/blog?category=Flashhttp://cliffmeyers.com/blog?category=Flexhttp://cliffmeyers.com/blog?category=Githttp://cliffmeyers.com/blog?category=Hibernatehttp://cliffmeyers.com/blog?category=HTML5http://cliffmeyers.com/blog?category=Javahttp://cliffmeyers.com/blog?category=JavaScripthttp://cliffmeyers.com/blog?category=jQueryhttp://cliffmeyers.com/blog?category=Localizationhttp://cliffmeyers.com/blog?category=Machttp://cliffmeyers.com/blog?category=Mustachehttp://cliffmeyers.com/blog?category=OOPhttp://cliffmeyers.com/blog?category=Sencha+Touchhttp://cliffmeyers.com/blog?category=Springhttp://cliffmeyers.com/blog?category=Subversionhttp://cliffmeyers.com/blog?category=Testinghttp://cliffmeyers.com/blog?category=Toolshttps://twitter.com/intent/follow?original_referer=http%3A%2F%2Fcliffmeyers.com%2Fblog%2F2013%2F4%2F21%2Fcode-organization-angularjs-javascript®ion=follow_link&screen_name=cliffmeyers&tw_p=followbuttonhttps://twitter.com/StoneBrewingCohttps://twitter.com/#!/search?q=%23StoneEasthttp://t.co/lFYrVY2eTahttps://twitter.com/#!/search?q=%23beerhttps://twitter.com/#!/search?q=%23craftbeerhttps://twitter.com/#!/search?q=%23RVAhttp://twitter.com/cliffmeyers/status/520398091686772736http://t.co/HxTQvul90phttp://twitter.com/cliffmeyers/status/516810495178059776https://twitter.com/trochettehttps://twitter.com/universalmindhttp://twitter.com/cliffmeyers/status/516015968166760448