Sponsored By Kids Can Text

AngularJS Code Organization

There has been a lot written about code organization in AngularJS. Of course, there are many schools of thought. Here are the best articles I've seen on them:

All of those have some great ideas for structuring your AngularJS code. My problem was with actually using them in two environments:

  • Raw development code
  • Minified development/production code in PhoneGap

When developing, it's inconventient to run Grunt to concat all my code just to get an idea of how things look in the browser. So, I need a code structure/process that lets me see changes live in the browser while also working when minified.

For a while, I was doing something like this where each directive/controller/service was actually in a separate file (way too busy if all kept in one file):

var app = angular.module('App', []);

app.directive('myGreatDirective', function(){  
    return {
        //...
    }
});

app.directive('myBetterDirective', function(){  
    return {
        //...
    }
});

app.controller('SomeController', [ '$scope', function($scope) {

}]);

app.controller('AnotherController', [ '$scope', function($scope) {

}]);

app.factory('BestService', [ function() {

}]);

That works fine.... Until you need to inject a service into your app.config. Then you're hosed because the service doesn't exist yet because it's further down in the code. Ugh.

Next, I tried using distinct modules for directives, controllers, and services. Then, I'd inject them into the main module. (Again, using separate files for each individual directive, controller, and service). Such as :

// One Directive File
angular.module('app.directives', [])  
    .directive('myGreatDirective', function(){
        return {
            //...
        }
    });

// Another Directive File
angular.module('app.directives', [])  
    .directive('myBetterDirective', function(){
        return {
            //...
        }
    });

...
...

angular.module('MyGreatApp', [ 'app.directives', 'app.services', 'app.controllers' ]);  

This seemed to work... But then I realized each directive file was replacing the previous directive module. So, in the end only one directive ever existed.

Finally, I figured out how to have individual files for each directive (or service or controller) without each replacing the previous module.

Note how only the very first module using the [].

// File : /js/directives/mainDirective.js
angular.module('app.directives',[]);

// File : /js/directives/myGreatDirective.js
angular.module('app.directives')  
    .directive('myGreatDirective', function(){
        return {
            //...
        }
    });

// File : /js/directives/myBetterDirective.js
angular.module('app.directives')  
    .directive('myBetterDirective', function(){
        return {
            //...
        }
    });

...
...

// File : /js/app.js
angular.module('MyGreatApp', [ 'app.directives', 'app.services', 'app.controllers' ]);  

Now, in my original development source, my index.html looks like this:

<head>  
    <meta charset="utf-8">
    <title>My Great App</title>
    <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">


    <link rel="stylesheet" href="css/app.css">

    <script src="/js/angular/angular.min.js"></script>
    <script src="/js/angular/angular-animate.min.js"></script>
    <script src="/js/angular/angular-route.min.js"></script>
    <script src="/js/angular/angular-touch.min.js"></script>
    <script src="/js/angular/angular-sanitize.min.js"></script>

    <!-- GRUNTSTART -->

    <script src="/js/directives/mainDirective.js"></script>
    <script src="/js/directives/myGreatDirective.js"></script>
    <script src="/js/directives/myBetterDirective.js"></script>

    <script src="/js/controllers/mainController.js"></script>
    <script src="/js/controllers/myGreatController.js"></script>
    <script src="/js/controllers/myBetterController.js"></script>

    <script src="/js/services/mainService.js"></script>
    <script src="/js/services/myGreatService.js"></script>
    <script src="/js/services/myBetterService.js"></script>

    <script src="/js/app.js"></script>

    <!-- GRUNTEND -->

</head>  
<html ng-app="MyGreatApp">  
</html>  

This allows me to develop locally and see changes instantly. Then, when I run Grunt, I use a contact and replace method to replace all of those individual files with a single concatenated file.

concat: {  
    js: {
        src: [
            'src/js/directives/mainDirective.js',
            'src/js/directives/**.js',

            'src/js/controllers/mainController.js',
            'src/js/controllers/**.js',

            'src/js/services/mainService.js',
            'src/js/services/**.js',

            'src/js/app.js'
        ],
        dest: 'www/js/app.concat.js'
    }
},
replace: {  
    html: {
        src: ['src/index.html'],
        dest: 'www/index.html',
        replacements: [
            {
                from: /<!-- GRUNTSTART -->[\s\S]*?<!-- GRUNTEND -->/g,
                to: '<script src="js/app.concat.js"></script>'
            }
        ]
    }
}

And ultimately end up with this:

<head>  
    <meta charset="utf-8">
    <title>My Great App</title>
    <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">

    <link rel="stylesheet" href="css/app.css">

    <script src="/js/angular/angular.min.js"></script>
    <script src="/js/angular/angular-animate.min.js"></script>
    <script src="/js/angular/angular-route.min.js"></script>
    <script src="/js/angular/angular-touch.min.js"></script>
    <script src="/js/angular/angular-sanitize.min.js"></script>

    <script src="js/app.concat.js"></script>

</head>

<html ng-app="MyGreatApp">  
</html>  

NOTE : There's a lot more to the Grunt process than this, but I just showed the relevant chunks. Some of this is based on the work of Sergiator