.Title

Use Gulp to ease your angularjs workflow

I’m often seeing people having trouble managing angular dependency injection, minification, templates etc. I never had such issues, because my gulp-based workflow already handles everything for me.

What’s wrong

For example, angular DI, even though they deprecated the awful array notation, is a pain in the ass. Yeah, it’s not DRY at all:

angular.module('myModule')
.controller('SomeCtrl', SomeCtrl)

function SomeCtrl($scope, $timeout) {}
//get the déjà-vu feeling?
SomeCtrl.$inject = ['$scope', '$timeout'];

Writing some templates, that angular will interprete so that they’ll be available in the $templateCache also sucks. Especially if you want some clean way of doing it. Using javascript, it would look a lot like this:

angular.module('myModule.templates')
.run(['$templateCache', function($templateCache) {
  $templateCache.put('views/list', '<ul><li></li></ul>')
}])

But, a template is HTML right? Why bother writing HTML inside Javascript? Actually I copied one of my gulp-rendered templates because I’ve never done that. Ok, I might but I was an angular newbie then.

Fix it!

Dependency injection

First, use ng-annotate! (FYI: first commit on Sep 3, 2013)

This is an amazing tool that will take care of the DI for you. You can now write:

function MyCtrl($scope) {}

And the plugin will add .$inject = ['$scope']. This is nice and allows you, on top of less keystrokes, to keep a cleaner code.

Templates

Write your templates using HTML, or HAML or whatever. Just don’t write crazy templates within javascript! This makes me remember of late 2006’s ; when writing HTML inside an awful PHP code made sense!

Gulping fancy

For the record, I’m following those angular style guide.

Take the following structure:

We need:

Optionally, you might want to use gulp-plumber to manage stream errors so that they don’t throw. Especially annoying when gulp exits while using gulp watch.

One-liner dependencies installation:

npm install gulp gulp-ng-annotate gulp-angular-templatecache gulp-minify-html  gulp-jsclosure gulp-uglify gulp-concat gulp-rename --save-dev

And now we can write the gulpfile:

// gulpfile.js
'use strict'
const gulp = require('gulp')
const templateCache = require('gulp-angular-templatecache')
const minifyHtml = require('gulp-minify-html')
const concat = require('gulp-concat')
const uglify = require('gulp-uglify')
const rename = require('gulp-rename')
const ngannotate = require('gulp-ng-annotate')
const closure = require('gulp-jsclosure')
const p = require('path')

/**
* Paths configuration, easy to change and/or use in multiple tasks
*/
const paths = {
  javascripts: [
    './src/js/app.js',
    /*
     * this file should not be commited to git, you write HTML!
     * it should also not beeing watched by gulp if it then triggers a change
     * or gulp will be left in an infinite loop (see below)
     */
    './src/js/templates.js',
    './src/**/*.js',
    './src/**/**/*.js'
  ],
  templates: [
    './src/templates/*.html',
    './src/templates/**/*.html'
  ]
}

/**
* Takes html templates and transform them to angular templates (javascript)
*/
gulp.task('templates', function() {
  return gulp.src(paths.templates)
   .pipe(minifyHtml({
        empty: true,
        spare: true,
        quotes: true
    }))
    .pipe(templateCache({
      module: 'myModule.templates',
      standalone: true,
      /**
       * Here, I'm removing .html so that `$templateCache` holds
       * the template in `views/home` instead of `views/home.html`.
       * I'm keeping the directory structure for the template's name
       */
      transformUrl: function(url) {
        return url.replace(p.extname(url), '')
      }
    }))
    //put all those to our javascript file
    .pipe(concat('templates.js'))
    .pipe(gulp.dest('./src/js'))
})

/**
* Concat, closure, annotate, uglify scripts
* @beforeDo `gulp templates`
*/
gulp.task('scripts', ['templates'], function() {
  return gulp.src(paths.javascripts)
    //first, I'm building a clean 'main.js' file
    .pipe(concat('main.js'))
    .pipe(closure({angular: true}))
    .pipe(ngannotate())
    .pipe(gulp.dest('./dist'))
    //then, uglify the `main.js` and rename it to `main.min.js`
    //mangling might cause issues with angular
    .pipe(uglify({mangle: false}))
    .pipe(rename('main.min.js'))
    .pipe(gulp.dest('./dist'))
})

/**
* The command `gulp` will resolve in `gulp scripts`
*/
gulp.task('default', ['scripts'])

/**
* Watch the paths you want and execute the scripts task
* @beforeDo default (small useful hack)
*/
gulp.task('watch', ['default'], function() {
  /**
   * Either don't add `templates.js` to the js paths 
   * and add it later to the scripts task source or remove it here.
   * Indeed, if `templates.js` is beeing watched, 
   * it'll run the templates task and it might change the file. 
   * The task will then run again, etc.
   * You can use https://github.com/urish/gulp-add-src to add it to the `script`
   * sources.
   */
  let js = paths.javascripts.slice()
  js.splice(js.indexOf('./src/js/templates.js'), 1)

  gulp.watch(js, ['scripts'])
})

And now we can code in HTML, javascript, and forget about .$inject or ugly javascripthtml, while coding!

To use the $templateCache in your app, you just have to add myModule.templates to the module dependencies and use $templateCache.get('views/home').

Feel the Gulp power

I hate HTML, maybe I wrote to much of it, maybe just because closing tags give me a “repeat yourself” feeling. Thankfully, there are many template engines that compiles to HTML those days. My preferred one is HAML, but the following will work the same with Jade, or any other.

So, I’m adding HAML to the workflow. HAML needs to be converted in HTML and then in Angular templates.

For HAML, the only plugin that always worked for me is the gulp-ruby-haml. Note that it is not really following gulp philosophy in which it just executes the haml command.

Let’s install this (you will need haml ruby gem):

npm install gulp-ruby-haml

Then simply add the dependency, change your templates extensions and add the haml to html transformation:

Index: gulpfile.js
===================================================================
--- gulpfile.js    2015-12-01 21:05:25.000000000 +0100
+++ gulpfile.js    2015-12-01 21:05:35.000000000 +0100
@@ -8,6 +8,7 @@
 const rename = require('gulp-rename')
 const ngannotate = require('gulp-ng-annotate')
 const closure = require('gulp-jsclosure')
+const haml = require('gulp-ruby-haml')
 const p = require('path')

 const paths = {
@@ -23,13 +24,14 @@
     './src/**/**/*.js'
   ],
   templates: [
-    './src/templates/*.html',
-    './src/templates/**/*.html'
+    './src/templates/*.haml',
+    './src/templates/**/*.haml'
   ]
 }

 gulp.task('templates', function() {
   return gulp.src(paths.templates)
+   .pipe(haml()) //that's the gulp magic we love
    .pipe(minifyHtml({
         empty: true,
         spare: true,

That’s it, you can now write HAML, and keep the exact same configuration.

Now enjoy coding with angularjs and your favorite template engine!