Angular Boilerplate Plus

Most of the Angular tutorials online use trivial examples to illustrate a very specific point and don’t provide any guidance on how to structure an Angular project. Unless your app is limited to one controller and one directive, it’s something you’ll need to think about. Fortunately, Josh D. Miller published Angular Boilerplate so we don’t need to think too hard.

What Angular Boilerplate, or ngbp, does is it tries to set forth some best practices for structuring a non-trivial angular apps. (Ironically, ngbp is not a sample app, so it provides a trivial app to demonstrate how to structure a non-trivial app.)

Building (grunt)

Out of the box ngbp gives you some handy grunt tasks.

grunt build - Builds/compiles less and coffeescript into css and javascript, respectively. Converts your templates into js to reduce the number of queries the application makes to the server. It copies everything into the build folder and runs your tests.

grunt watch - Builds then rebuilds if it detects any changes to the source files (i.e. files in src/ and vendor/). The caveat here is that it will only detect changes to existing files. If you add a new module or template, you’ll need to stop and restart grunt watch.

grunt compile - Concatenates and minifies all the css and javascript files in your build folder into one neatly packaged css and javascript file in your bin folder.

Routing (ui-router)

ui-router let’s you organize your app and urls around states, instead of trying to organize your app around urls. States can be nested and child states inherit their parent’s scope. Functionally this means that you can define a parent state which lists all of the objects in a collection and clicking an object transitions the user to a child state (for example, a detail view of the selected object). The child state has access to the collection defined in the parent state, so you don’t need to re-fetch the object from the server to display it. Changes made to the object in the child state are reflected in the parent state when the user transitions back to it.

Compiling (ngmin)

When I started with ngbp, I was surprised that the sample modules weren’t annotated. Everybody knows you need annotations if you want to minify your angular app! Right? In my naiveté I started adding all the missing annotations.

// Breaks when minified
angular.module('myMod', [])
.controller('MyController', function($scope) {
    $scope.key = value;
});

// Works when minified
angular.module('myMod', [])
.controller('MyController', [ '$scope',
    function MyController (    $scope) {
    $scope.key = value;
});

// I even line up the names to make sure I've got them all

It turns out ngbp wasn’t raised by no fool and you don’t need to write annotations, because the annotations are added automatically by ngmin during the compile step. Very clever! Except ngmin doesn’t annotate all dependency injections, most notably, those injected into resolves in a ui-router state.

angular.module('myApp.area', [ 'ui-router'] )

// Annotated by ngmin
.config(function($stateProvider) {
    $stateProvider
    .state('myState', {
        url: '/items/:id',
        controller: 'StateCtrl',
        resolve: {
            // NOT annotated by ngmin
            item: function($http, $stateParams) {
                return $http.get('/api/' + $stateParams['id'])
                .then(
                    function success(response) {
                        return response;
                    }
                )
            }
        }
    })
});

Fortunately, there is a simple fix: ng-annotate. ng-annotate has nominated itself to be the successor to ngmin and has the backing of the author of ngmin himself, so it’s probably just a matter of time before ngbp ships with ng-annotate, but until it does, you’ll need to switch manually.

`# Remove ngmin and it's dependencies`
npm uninstall grunt-ngmin --save-dev
`# Install grunt-ng-annotate and it's dependencies`
npm install grunt-ng-annotate --save-dev
`# Update the grunt tasks to use grunt-ng-annotate`
sed -i "" -e 's/grunt-ngmin/grunt-ng-annotate/g' Gruntfile.js
sed -i "" -e 's/ngmin/ngAnnotate/g' Gruntfile.js
`# Not strictly necessary, just updates a comment`
sed -i "" -e 's/ng-min/ngAnnotate/g' Gruntfile.js

Settings (environment variables)

It is not uncommon to use different variables in different environments. For example, a local api endpoint for development and the real api endpoint for production. While ngbp doesn’t support it out of the box, it’s easy enough to add yourself.

  1. Create your environment specific configuration files
// file: src/conf/dev.js
angular.module('myApp.settings', [])
.constant('config', {
    'html5Mode': false
});
// file: src/conf/prod.js
angular.module('myApp.config', [])
.constant('settings', {
    'html5Mode': true
});
  1. Use the values in your app
// file: src/app.js
angular.module('myApp', [
    'myApp.config'
])
.config(function($locationProvider, settings) {
    $locationProvider.html5Mode(settings.html5Mode);
})

...

;
  1. Set a default environment
// file: build.config.js
module.exports = {
    ...
    env: 'dev',
    ...
  1. Update your build task
// file: Gruntfile.js

...

// Rename the build task to ngbpbuild
grunt.registerTask( 'ngbpbuild', [
    'clean', 'html2js', 'jshint', 'coffeelint', 'coffee', 'less:build', 'concat:build_css', 'copy:build_app_assets', 'copy:build_vendor_assets', 'copy:build_appjs', 'copy:build_vendorjs', 'index:build', 'karmaconfig', 'karma:continuous'
]);

// Create a new env aware build task
// Sets the env and calls the old (renamed) build task
grunt.registerTask( 'build', function(env) {
    env = env || grunt.config.get("env") || "dev";
    grunt.config.set("env", env);
    grunt.task.run("ngbpbuild");
});

...

Conclusion

I’d recommend ngbp as a starting point for any angular app, even trivial ones, because weren’t all non-trivial apps trivial at some point?

The magic of ngbp is in the Gruntfile. By automatically discovering and adding project files to index.html during grunt build, ngbp makes it easy to create and maintain a sensible project structure, even if it’s not the structure they prescribe. And ng-annotate and grunt watch, a grunt task that detect changes, rebuilds the project and reloads your browser, eliminate a lot of the repetition of angular development. Good project structure and automating repetitive tasks are a recipe for greater productivity and ultimately that’s the goal-to be productive.