AngularJS, Coding

AngularJS Modules for Great Justice

First off I want to thank Joel Hooks of the DFW Area AngularJS Meetup Group for suggesting this topic and providing the title.

“Modules provide an excellent mechanism for cleanly dividing up our code into functional areas. Angular allows us to use modules in many ways. Let’s take a look at modules and some of the approaches we can leverage to produce cleaner more manageable code.” – Joel Hooks

So with that, let’s get started.

AngularJS modules are the core means by how you define the components in your app. Besides defining your components, modules provide a way to indicate the dependencies your components require and they help you organize your components to help you write modular code that can be re-used across applications.

As long as I have been developing with AngularJS there has always been the great best practices debate over how to structure your application. Do you use a package by feature or package by layer approach?

Both have their advantages and disadvantages so let’s take a quick look at each before we get into how to implement each using AngularJS.

Package by Feature

Package by Feature became popular in the Java development camp a few years back. The main tenet is that by keeping all the source code related to a specific feature in a single package, it was easier to develop and promote modularity across your source code. Entire features could be lifted from applications and re-used in others by just taking the source package, there was no need to bring in other packages since all the code for the feature was in one place.

Package-by-feature uses packages to reflect the feature set. It places all items related to a single feature (and only that feature) into a single directory. This results in packages with high cohesion and high modularity, and with minimal coupling between packages. Items that work closely together are placed next to each other. They aren’t spread out all over the application.

A lot of developers feel that by using Package by Feature, development on large projects is also easier since all the code you need to deal with is in one place and as the project grows following the pattern keeps everything well organized.

A good example of Package by Feature, is the angular-sprout seed project. All of the JavaScript files for a particular feature are included in the same directory. So, if we have three different views in our application; User, Movie, and Rating, we’ll have three directories in our source code. Each folder would include the source for the controllers, services, models and templates for the specific feature.

Package by Layer

If you come from a object-oriented development background, you probably are more familiar with the Package by Layer approach. The basic tenets of object-oriented design is to design by layers; data access, business logic, business entities, and user interface. This way your code is organized in layers that communicate with each other via specific interfaces. Depending on how rigid your design and coding rules are, layers only interacted with adjacent layers and never called across boundaries.

In package-by-layer, the highest level packages reflect the various application “layers”, instead of features. Each feature has its implementation spread out over multiple directories, over what might be loosely called “implementation categories”. Each directory contains items that usually aren’t closely related to each other. This results in packages with low cohesion and low modularity, with high coupling between packages.

Package by Layer works great when it comes to code that implements cross cutting concerns, however when you are developing features that span multiple layers development becomes harder since you have to deal with source files in various places. Package by Layer also promotes cross package dependencies which some feel are a bad thing.

A good example of Package by Layer, is the angular-seed project. All of the JavaScript files for a particular type of AngularJS component are included in the same directory or file. So, our application would be divided across the following source folders; controllers, directives, services, models, filter, etc.

So What’s All This Got to do with AngularJS Code

So, you’re probably wondering, “Why should I care, I write web apps?” Actually how you partition your code has a lot to do with how you organize your project source code and how much easier it is to maintain as your project grows.

Depending on the size of your project your are going to have to decide on how you want your code structured. Do you keep everything in a single JavaScript file, do you break your files based on layers, do you break your files based on features, or do you structure your code directories by features and structure your source files by layers?

Tiny Projects

For me, if I have a very small or tiny project that does one thing, I might keep all of my source code in a single file using single module. Below is a picture of the project’s directory structure. Notice we have a single file, app.js which holds our source code and our index.html file which loads and bootstraps our app.

Below is the app.js file, I’ve defined a single module called myApp and used the controller and directive definition methods to define my controller and directive used by my app. Everything is in one place and easy to maintain, which is all you need when writing a very simple application.

        angular.module('myApp', [])

        // register the controller for the app
        .controller('myController',['$scope', function($scope){
            $scope.format = 'M/d/yy h:mm:ss a';
        }])

        // Register the 'myCurrentTime' directive factory method.
        // We inject $timeout and dateFilter service since the factory method is DI.
        .directive('myCurrentTime', function($timeout, dateFilter) {
            // return the directive link function. (compile function not needed)
            return function(scope, element, attrs) {
                var format,  // date format
                    timeoutId; // timeoutId, so that we can cancel the time updates

                // used to update the UI
                function updateTime() {
                    element.text(dateFilter(new Date(), format));
                }

                // watch the expression, and update the UI on change.
                scope.$watch(attrs.myCurrentTime, function(value) {
                    format = value;
                    updateTime();
                });

                // schedule update in one second
                function updateLater() {
                    // save the timeoutId for canceling
                    timeoutId = $timeout(function() {
                        updateTime(); // update DOM
                        updateLater(); // schedule another update
                    }, 1000);
                }

                // listen on DOM destroy (removal) event, and cancel the next UI update
                // to prevent updating time after the DOM element was removed.
                element.bind('$destroy', function() {
                    $timeout.cancel(timeoutId);
                });

                updateLater(); // kick off the UI update process.
            }
        });

Below is the index.html file that instantiates the app and displays an input field that allows you to modify the time format that is used by the myCurrentTime directive to display the current date and time to the user.

        <!DOCTYPE html>
        <html ng-app="myApp">
        <head>
            <title></title>
            <script src="lib/angular/angular.js" type="text/javascript"></script>
            <script src="app/app.js" type="text/javascript"></script>
        </head>
        <body>
        <div ng-controller="myController">
            <input type="text" ng-model="format"/>
            <div>Time format is: {{format}}</div>
            <div>Current time is: <span my-current-time="format"></span></div>
        </div>
        </body>
        </html>

Small Projects

As your project starts to get a little more complicated you might want to start organizing your code into multiple modules, but still keep it in a single file. Below is a picture of the project’s directory structure. Notice we still have a single file, app.js which holds our source code and our index.html file which loads and bootstraps our app.

Below is the app.js file, I’ve defined multiple modules one for the controllers, a second for the directives and a third for the app. I have again used the controller and directive definition methods to define my controller and directive used by my app, but now they are defined on specific modules defined for each type of AngularJS component type. I have also added dependencies to the application’s module definition so everything get injected properly. Everything is in one place and easy to maintain, but is ready for when you need to separate your code into multiple files when they get too big to maintain.

        angular.module('myControllers', [])
            // register the controller for the app
            .controller('myController',['$scope', function($scope){
                $scope.format = 'M/d/yy h:mm:ss a';
            }]);

        angular.module('myDirectives', [])
            // Register the 'myCurrentTime' directive factory method.
            // We inject $timeout and dateFilter service since the factory method is DI.
            .directive('myCurrentTime', function($timeout, dateFilter) {
                // return the directive link function. (compile function not needed)
                return function(scope, element, attrs) {
                    var format,  // date format
                        timeoutId; // timeoutId, so that we can cancel the time updates

                    // used to update the UI
                    function updateTime() {
                        element.text(dateFilter(new Date(), format));
                    }

                    // watch the expression, and update the UI on change.
                    scope.$watch(attrs.myCurrentTime, function(value) {
                        format = value;
                        updateTime();
                    });

                    // schedule update in one second
                    function updateLater() {
                        // save the timeoutId for canceling
                        timeoutId = $timeout(function() {
                            updateTime(); // update DOM
                            updateLater(); // schedule another update
                        }, 1000);
                    }

                    // listen on DOM destroy (removal) event, and cancel the next UI update
                    // to prevent updating time after the DOM element was removed.
                    element.bind('$destroy', function() {
                        $timeout.cancel(timeoutId);
                    });

                    updateLater(); // kick off the UI update process.
                }
            });

        angular.module('myApp', ['myControllers', 'myDirectives']);

Notice that the index.html file used in our example below hasn’t changed.

        <!DOCTYPE html>
        <html ng-app="myApp">
        <head>
            <title></title>
            <script src="lib/angular/angular.js" type="text/javascript"></script>
            <script src="app/app.js" type="text/javascript"></script>
        </head>
        <body>
        <div ng-controller="myController">
            <input type="text" ng-model="format"/>
            <div>Time format is: {{format}}</div>
            <div>Current time is: <span my-current-time="format"></span></div>
        </div>
        </body>
        </html>

Medium Projects

As your projects get larger and more complex, you might want to follow the angular-seed approach and split your source code across layers. In the sample code, I’ve broken the code out across multiple source files by layer, there is one file for the app, one for the controllers and one for the directives. Depending on how you want to structure your code, you can use a single module or multiple modules. In this example I use a single module that is assigned to a variable that is referenced by the other source files.

Our source code structure has changed. Now, instead of just an app directory there is now a controllers and directives directory.

Below is the contents of the app.js file. All we are doing is declaring the module that will be used by app:

        var myModule = angular.module('myApp', []);

Again, I am using a single module across my entire app so I am assigning it to variable, which I will reference in the other source files to define my controller and directive.

Below is the contents of the controllers.js file. Here we are using the myModule variable to define the controller.

        // register the controller for the app
        myModule.controller('myController',['$scope', function($scope){
            $scope.format = 'M/d/yy h:mm:ss a';
        }]);

Below is the contents of the directives.js file. Again we are using the myModule variable to define the directive.

        myModule.directive('myCurrentTime', function($timeout, dateFilter) {
            // return the directive link function. (compile function not needed)
            return function(scope, element, attrs) {
                var format,  // date format
                    timeoutId; // timeoutId, so that we can cancel the time updates

                // used to update the UI
                function updateTime() {
                    element.text(dateFilter(new Date(), format));
                }

                // watch the expression, and update the UI on change.
                scope.$watch(attrs.myCurrentTime, function(value) {
                    format = value;
                    updateTime();
                });

                // schedule update in one second
                function updateLater() {
                    // save the timeoutId for canceling
                    timeoutId = $timeout(function() {
                        updateTime(); // update DOM
                        updateLater(); // schedule another update
                    }, 1000);
                }

                // listen on DOM destroy (removal) event, and cancel the next UI update
                // to prevent updating time after the DOM element was removed.
                element.bind('$destroy', function() {
                    $timeout.cancel(timeoutId);
                });

                updateLater(); // kick off the UI update process.
            }
        });

Finally below is the index.html file used to load and instantiate the app. Notice how we now need to include the new source files in order to load all of our code.

        <!DOCTYPE html>
        <html ng-app="myApp">
        <head>
            <title></title>
            <script src="lib/angular/angular.js" type="text/javascript"></script>
            <script src="app/app.js" type="text/javascript"></script>
            <script src="controllers/controllers.js" type="text/javascript"></script>
            <script src="directives/directives.js" type="text/javascript"></script>
        </head>
        <body>
        <div ng-controller="myController">
            <input type="text" ng-model="format"/>
            <div>Time format is: {{format}}</div>
            <div>Current time is: <span my-current-time="format"></span></div>
        </div>
        </body>
        </html>

Large to Very Large Projects

As your application starts to grow and include more and more features, it is best to re-structure your source code’s directory structure by using the Package by Feature pattern and then use the Package by Layer pattern for your source files that relate to the feature. Below is a picture of the Large project’s structure. Now, we have created the folder structure based on the features in the app and the source files are based on the different AngularJS layers.

Below is the contents of the app.js file. We are again defining a module for our app. Notice that we have included the names of the other modules which define our features.

        angular.module('myApp', ['my-view-controller', 'time']);

Below is contents of the my-view-controller.js app, which defines our controller for the feature.

        // register the controller for the app
        angular.module('my-view-controller', []).controller('myController',['$scope', function($scope){
            $scope.format = 'M/d/yy h:mm:ss a';
        }]);

Since we are using the Package by Feature pattern to structure our code, we have now created a template that will be used to instantiate our controller. The reason for breaking out the HTML for the feature is because we want to have a fully re-usable package that we can pick up and drop into another application without having to drag other parts of the source application into your new app.

Below is the contents of my-view-partial.html:

        <div ng-controller="myController">
            <input type="text" ng-model="format"/>
            <div>Time format is: {{format}}</div>
            <div>Current time is: <span my-current-time="format"></span></div>
        </div>

We have also moved our directive to it’s own feature directory giving us another module that we can reuse in other applications.

        // Register the 'myCurrentTime' directive factory method.
        // We inject $timeout and dateFilter service since the factory method is DI.
        angular.module('time', []).directive('myCurrentTime', function($timeout, dateFilter) {
            // return the directive link function. (compile function not needed)
            return function(scope, element, attrs) {
                var format,  // date format
                    timeoutId; // timeoutId, so that we can cancel the time updates
        
                // used to update the UI
                function updateTime() {
                    element.text(dateFilter(new Date(), format));
                }
        
                // watch the expression, and update the UI on change.
                scope.$watch(attrs.myCurrentTime, function(value) {
                    format = value;
                    updateTime();
                });
        
                // schedule update in one second
                function updateLater() {
                    // save the timeoutId for canceling
                    timeoutId = $timeout(function() {
                        updateTime(); // update DOM
                        updateLater(); // schedule another update
                    }, 1000);
                }
        
                // listen on DOM destroy (removal) event, and cancel the next UI update
                // to prevent updating time after the DOM element was removed.
                element.bind('$destroy', function() {
                    $timeout.cancel(timeoutId);
                });
        
                updateLater(); // kick off the UI update process.
            }
        });

Finally, we have our index.html file which loads our source and instantiates our app. Notice that we have a script tag for each of our source files and that we are using the ng-include directive to load our view’s template.

        <!DOCTYPE html>
        <html ng-app="myApp">
        <head>
            <title></title>
            <script src="lib/angular/angular.js" type="text/javascript"></script>
            <script src="app/app.js" type="text/javascript"></script>
            <script src="myView/my-view-controller.js" type="text/javascript"></script>
            <script src="time/time-directive.js" type="text/javascript"></script>
        </head>
        <body>
        <div ng-include src="'myView/my-view-partial.html'"></div>
        </body>
        </html>

Use What Works Best for Your App

So we’ve covered several patterns that can be used to organize your source code, which one you use is up to you. Below are a couple of quick guidelines for writing different types of apps or re-usable components:

  • Directives and Components – If possible try to keep your code to a single module and single source file. If you have templates that your directive or component depends upon, either provide an attribute or method that allows end users to provide a templat Url or include a second JavaScript file that loads the templates via code and adds them to the template cache.
  • Re-Usable Feature Modules – Use the Package by Feature pattern to keep everything in a single directory that can easily be added to a project. Whether you use separate template files or include them in a source file is up to you, lean towards what works best if the module templates need to be tailored for use in the app they are added to. If you plan on making the module deployable via Bower or npm, plan on including your unit tests so end users can use them to ensure everything works as plan.
  • Simple Apps – Use a single source module and source file to reduce the number of files that need to be loaded at run time. If you have multiple templates in your app, consider putting them into the index.html file and use ng-show, ng-hide or ng-switch to display them as needed.
  • Large Complex Apps – Use the Package by Feature pattern to structure your source code into modules and then use the Package by Layer pattern for your source files.

Where Do I Put My Unit Tests?

Another question that comes up when talking about source code structure is “Where do I put my unit tests?”

I’m a believer that unit tests should be under a seperate folder called ‘unittests’ that has the same directory structure as your app. If you structure your app by feature, then your unit tests should also be structured by feature. This helps with maintenance since the directory structure is the same as the source code’s structure.

The only time to I would include the unit tests with the actual source files is if you are create a package that will be distributed by a package manager such as Bower or npm. Then I think it’s more important so the end user has a way to validate your directive or component works as intended.

What you do is ultimately up to you. Those are the decisions that you should make based on your team’s workflow.

The Wrap Up

In this article I’ve covered a couple of different patterns that you can use to organize your AngularJS source code and how modules can be used to help you organize your code and it’s dependencies. Hopefully, this helps you come up with a project structure that works best for you and your team.

Source Code

You can find all of the source code for this article on my GitHub page at https://github.com/lavinjj/angularjs-modules-for-great-justice

Standard
Loading Facebook Comments ...

18 thoughts on “AngularJS Modules for Great Justice

  1. Pingback: JavaScript | Pearltrees

  2. Pingback: AngularJS | Pearltrees

  3. Pingback: AngularJS Tutorials Collection | esgy

  4. Pingback: Angular Gotcha's, Tips, Tricks & Best Practices | Pearltrees

  5. Luís Felipe de Andrade says:

    Thanks man, i really need this. It`s simply not possible to manage a big angular project using the organization of the “seed” it`s very hard to maintain. Thanks a lot.

    I would l

  6. Pingback: AngularJS Highlights: Week Ending 10 November 2013 | SyntaxSpectrum

  7. Pingback: JS | Pearltrees

  8. Pingback: AngularJS | Pearltrees

  9. Pingback: AngularJS: taking full advantage of AngularJS features/structures | Webby Expert

  10. Pingback: AngularJS-Learning | Nisar Khan

  11. Matt says:

    Great post. I would appreciate knowing what you think about another scenario not addressed directly in this post. What about the scenario where you have a large web application, wherein different ng-apps exist; or put another way, how do you structure a large site composed of separate “mini-spa” applications that have some code sharing?

    Scott Allen mentions this issue in his OdeToCode blog:
    http://odetocode.com/blogs/scott/archive/2013/05/07/angularjs-abstractions-organizing-modules.aspx
    “One scenario I haven’t found addressed very well is the scenario where multiple apps exist in the same greater web application and require some shared code on the client.”

  12. Pingback: AngularJS: structuring a web application with multiple ng-apps - FAQs System

  13. Pingback: AngularJS: structuring a web application with multiple ng-apps | Question and Answer

  14. Pingback: How could you become zero to hero in AngularJS? | Milap Bhojak

Got a comment? I'd love to hear it!