Structure for your large Angular or JavaScript app

February 23, 2015

JavaScript frameworks don’t generally come with many opinons on how you organize your applications. You could be using a single file or many, a single module or a multitude, organized in whichever way you think is best. And when it comes to organizing a large applications, a number of developers (including us) are left scratching their heads wondering if they’re doing it right or even just how to get started. So when we started building our AngularJS application, we researched and found a few popular ways to organize your front-end code and decided on one we liked best.

Let’s consider a hypothetical app that allows users to maintain a garden and see how we might organize it.

Functionality-based directory structure

This is perhaps the most popular way of organizing an Angular project, but not necessarily the best. The structure relies on folders of files with similar functionality. As an example:

/scripts
  app.js
  /config
    global-config.js
    account-config.js
    artichoke-config.js
    cucumbers-config.js
    garden-config.js
  /constants
    global-constants.js
  /controllers
    account-controller.js
    artichokes-controller.js
    garden-controller.js
    cucumbers-service.js
    navigation-controller.js
  /services
    account-service.js
    artichokes-service.js
    cucumbers-service.js
    garden-service.js
  /directives
    active-route-directive.js
    garden-drag-drop-directive.js
    artichokes-directive.js
    cucumbers-directive.js
  /templates
    account-template.html
    artichokes-item-template.html
    artichokes-list-template.html
    cucumbers-item-template.html
    cucumbers-list-template.html
    navigation-template.html
    garden-item-template.html
  /values
    global-providers.js
/tests
  ...

Usually this structure works out great for small applications. Unfortunately, it starts to breakdown in quite a number of ways when scaling to a larger application:

  • Where would you specify modules, their dependencies, configs and run methods?
  • Difficult to identify all files related to a feature: active-route-directive.js + navigation-controller.js + navigation-template.html + navigation-config.js are all necessary to render the navigation properly, but you wouldn’t know it by glancing. Which also means:
  • Long-term maintenance woes. If we were to replace our garden with a terrarium, how are we to easily know what’s affected? What if we completely removed it?
  • Deciding if Angular vocabulary items are really worth their own folders: values, constants, providers, services, factories, etc.

As you can see, there’s a number of issues that will need to be addressed if an application continues on with this structure, which has lead many people to the next organizational option.

Feature-based directory structure

One way developers are addressing the problems with functionality-based organization in Angular applications is by structuring their code based on features instead. The application might look something like this:

/scripts
  app.js
  global-config.js
  global-constants.js
  global-providers.js
  /account
    account-config.js
    account-controller.js
    account-service.js
    account-template.html
  /artichokes
    artichokes-config.js
    artichokes-controller.js
    artichokes-item-template.html
    artichokes-service.js
    artichokes-directive.js
  /cucumbers
    cucumbers-config.js
    cucumbers-controller.js
    cucumbers-item-template.html
    cucumbers-service.js
    cucumbers-directive.js
  /garden
    garden-config.js
    garden-controller.js
    garden-item-template.html
    garden-service.js
    garden-drag-drop-directive.js
  /navigation
    active-route-directive.js
    navigation-config.js
    navigation-controller.js
    navigation-template.html
/tests
  ...

With this feature-based structure, we can now clearly identify everything that’s being included within our application and easily find necessary files to fix a bug in our cucumbers feature. Or, if we wanted to completely remove our garden feature, we’d be able to do so quickly and efficiently.

However, we still see room for improvement:

  • Often times some features aren’t intended to be used by others. How can we make that more obvious? On the flipside, how can we make it obvious which components are for reuse?
  • How can we incorporate 3rd party libraries?
  • What about global configuration files that aren’t necessarily part of any particular feature?

Combination directory structure

In the end we chose to combine the two with what we see as a pretty useful way to structure a large Angular application. It looks a little something like this:

/scripts
  app.js
  app.test.js
  /config
    global-config.js
    global-constants.js
    global-providers.js
  /libs
    ...
  /modules
    /account
      account-config.js
      account-service.js
    /navigation
      active-route-directive.js
      navigation-config.js
      navigation-controller.js
      navigation-template.html
    /artichokes
      artichokes-config.js
      artichokes-controller.js
      artichokes-item-template.html
      artichokes-service.js
      artichokes-directive.js
    /cucumbers
      cucumbers-config.js
      cucumbers-controller.js
      cucumbers-item-template.html
      cucumbers-service.js
      cucumbers-directive.js
    /garden
      garden-config.js
      garden-service.js
      garden-drag-drop-directive.js
  /routes
    /account
      account-controller.js
      account-template.html
    /garden
      garden-controller.js
      garden-item-template.html
    /home
      home-controller.js
      home-template.html
  /templates
    404.html

Global config

As an application expands, it’ll often find its own sets of configuration necessities that span across features. In our own environment, this is where we store things like whether our app runs in html5 mode, what base API URL to use, or predefined lists that may need to be rendered in various places.

Third party module separation

Sometimes it’s necessary to include third party integrations. Usually you’ll be using bower or npm to track these, but integration may often be required into your framework of choice. You now have a place other developers are able to quickly recognize where third party code is being integrated.

Elevated routes for quicker identification

For the time being, web apps are still heavily influenced by the routes that users can access. These accessible pages often come with their own very specific controllers and templates that combine the various modules of an application. In our mixed approach, we’ve recognized their status and elevated them within our code organization. This makes it extremely easy when a bug is reported on /garden for a developer who wasn’t involved in the feature to isolate the files and debug from there.

What do you think?

The examples above are very rudimentary, but with this mixed approach we think we’re getting the best of both the feature and functional worlds.

What do you think? Curious why I did something? Ask away, I’d love to answer.

This article originally appeared on SourceClear’s blog and is preserved here for historical reference.