Modular AngularJS App Design

angular module

I am a sucker for modules. Something about bundling a set of functionality and display logic into a module that can be easily redistributed for many apps makes my skin tingle. So when I saw that “modules” were a big part of the AngularJS methodology, I was super excited to try it out. I quickly realized however, that most of the examples online used one module for the entire application, which didn’t seem all that modular to me.

At my current company we are developing an app that is ideal for the modular approach. We essentially have separate “apps” within our application, each of which we have bundled into full angular modules. This methodology could be used for many other applications as well so I thought I would share our approach.

Note: Credit for this structure design goes to my coworker and friend @tphalp who is rapidly becoming an angular expert and teaching me his ways.

Directory Structure

Here is the way we setup our directory structure:

App Structure

You can see we have the standard app directory similar to angular seed or a yeoman generated project. But we add a modules directory within the app directory. Each module then has its own sub-directory and a file for directives, controllers, filters and services and a directory for views.

I am still somewhat conflicted on wether I should be creating a new file for each directive/controller/filter/service or if the current way is fine. Sometimes the files get to be too large an unwieldy and I want them broken up.

here is a shot further down the tree:

More app structure

We still maintain the scripts folder which houses app.js and our routes.js file for handling the routing.

Defining the Modules

Let’s have a look at the app.js file where we setup our modules and define dependencies.

// Define all your modules with no dependencies
angular.module('BirthdayApp', []);
angular.module('CollectionApp', []);
angular.module('DashboardApp', []);
angular.module('LoginApp', []);
angular.module('MessageApp', []);
angular.module('PatientApp', []);
angular.module('PhoneApp', []);
angular.module('ReportsApp', []);

// Lastly, define your "main" module and inject all other modules as dependencies
angular.module('MainApp',
  [
    'BirthdayApp',
    'CollectionApp',
    'DashboardApp',
    'LoginApp',
    'MessageApp',
    'PatientApp',
    'PhoneApp',
    'ReportsApp',
    'templates-main',
  ]
);

 

Creating a “MainAppModule” allows us to inject all our other modules which in turn allows each of our other modules to access each other. So if the Reports module needs to access something from the Patient module, it will be able to with this method without having to inject the Patient module directly into the Reports module.

The MainApp also allows us to do routing and use ng-view in our index.html file:

<html>
// ...
<body ng-app="MainApp">
    <div ng-view></div>
</body>
</html>
 

Then you define your routes on the MainApp module

// scripts/routes.js
angular.module('MainApp')
  .config(
    ['$routeProvider',
      function($routeProvider) {
        $routeProvider
          .when('/', {
            templateUrl: 'modules/dashboard/views/index.html',
            action: 'DashboardApp.DashboardCtrl'
          })
   // ...

Implementing Your Modules

Now when we go to add a directive/service/filter/controller to a module, we open up the appropriate file within that module and simply use the appropriate module name when we define it.

// app/modules/patient/controllers.js
angular.module('PatientApp').controller('PatientCtrl', function($scope) {
 $scope.patients = "something";
});
 

Since we inject all modules into a “global” module you will need to take care to namespace your angular elements appropriately so you don’t run into name conflicts. Take a look at angular bootstrap code too see how they use multiple modules and namespace them.

And that is it! Now you can make reusable modules for angular that you can use in different apps, or even contribute your module to Bower if it is something that will benefit the community.

Hope this helps!