Minifying an existing AngularJS project


Being a PHP Developer who only recently started using AngularJS I knew almost nothing about the incredible progress front-end development had made since I last created a website.

When we deployed our first AngularJS application over at Qandidate.com a single page view would generate a whopping 56 HTTP requests of which 32 were our own AngularJS files like controllers and services and 12 were 3rd party dependencies.

Since browsers only allow for up to about 8 parallel connections a lot of requests will have to wait for other requests to finish resulting in a poor end-user experience.

In this blog post I will show how you can safely minify and concatenate your AngularJS code.

Optimize all the code

While catching up on the latest technologies I came across this great post by @andersjanmyr. I also attended Aurelio de Rosa's talk "Modern front-end with the eyes of a PHP developer" at Dutch PHP Conference.

They introduced me to Grunt for running tasks and Bower for front-end package management.

Having followed @andersjanmyr's blog post I ended up with a really small set of concatenated and minified assets. Yay!

However, my AngularJS app was broken. Oh noes!

Minify AngularJS part 1

As with many problems the root cause was me :) Or at least me not properly using AngularJS's Dependency Injection. Being lazy I regularly injected dependencies like this:

myApp.controller('MyCtrl', function($q) {});

Minifying this will mess up the name of the dependency. If you annotate it like this it will work correctly:

myApp.controller('MyCtrl', ['$q', function($q) {}]);

So all I had to do was update my JavaScript... Great...

Luckily there is a Grunt plugin called grunt-ng-annotate based on ng-annotate which helps you out by applying just these fixes to your AngularJS JavaScript so it can be safely minified.

ng-annotate

Note: All the code I created for this post is available at GitHub.

You can install ng-annotate using npm:

# install globally
sudo npm install -g grunt-ng-annotate

# or install locally
npm install grunt-ng-annotate --save-dev

Note: The save-dev option will add the package to your package.json file under devDependencies.

In your Gruntfile.js load the plugin's tasks:

grunt.loadNpmTasks('grunt-ng-annotate');

Hint: look in to matchdep or load-grunt-tasks to load grunt plugins automatically.

Now register the ngAnnotate task in Gruntfile.js:

grunt.loadNpmTasks('ngAnnotate');

To demonstrate ng-annotate I created a controller without annotations called 'WithoutAnnotationsCtrl.js':

angular.module("MyMod").controller("MyCtrl", function($scope, $timeout) {
});

I want this file to be processed into:

angular.module("MyMod").controller("MyCtrl", ['$scope', '$timeout', function($scope, $timeout) {
}]);

In order to do so I added the following task config to Gruntfile.js:

grunt.initConfig({
    // Task configuration.
    ngAnnotate: {
        demo: {
            files: {
                'WithAnnotationsCtrl.js': ['WithoutAnnotationsCtrl.js']
            },
        }
    }
  });

This will tell ng-annotate to process WithoutAnnotationsCtrl.js and save the result as WithAnnotationsCtrl.js.

You can run the task with grunt ngAnnotate or simply grunt.

Note: I you haven't installed Grunt yet, you can do so by running:

sudo npm install -g grunt-cli

Have a look at WithAnnotationsCtrl.js and you will see the dependencies are nicely annotated. Now you can safely minify your code!

Minify AngularJS part 2

I will use grunt-contrib-uglify to minify.

Install it with:

npm install grunt-contrib-uglify --save-dev

Load the task in Gruntfile.js:

grunt.loadNpmTasks('grunt-contrib-uglify');

And add it to the default task AFTER ngAnnotate:

grunt.registerTask('default', ['ngAnnotate', 'uglify']);

Now I want the annotated JavaScript to be minified to WithAnnotationsCtrl.min.js. Just add the task configuration to Gruntfile.js:

grunt.initConfig({
    // Task configuration.
    // ngAnnotate: { ... },
    uglify: {
        demo: {
            files: {
                'WithAnnotationsCtrl.min.js': ['WithAnnotationsCtrl.js']
            }
        }
    }
  });

Now run grunt again and you will end up with a safely minified file:

angular.module("MyMod").controller("MyCtrl",["$scope","$timeout",function(){}]);

Wrap up

ng-annotate is great tool to clean up your existing AngularJS applications. It made me aware why coding standards matter in front-end as well as back-end applications.

As olov points out in ng-annotate's README AngularJS 1.3 will add a ngStrictDi argument to ng-app which when enabled will make your app fail when you don't use function annotation.