Code Organization in Large AngularJS and JavaScript Applications — Cliff Meyers
description
Transcript of Code Organization in Large AngularJS and JavaScript Applications — Cliff Meyers
-
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