Angular and RequireJS - Part 1

The problem – Nasty dependency ordering issues and loads of horrible script imports

In a nutshell

After recently switching my focus from Backbone to Angular I found I was hitting fairly major stumbling blocks regarding script importing, dependency ordering and deployment bundling when building anything but very simple applications.

In a bit more detail

As you find out fairly quickly with Angular, the seed project is great for getting started, but larger scale apps require a bit more thought structure-wise. Since controllers, services and directives should all be defined in separate files, and separate areas of functionality on the site divided into separate modules, doing things the old fashioned way, you end with a lot script tags in your HTML document, and all sorts of issues regarding the order in which these are specified.

A secondary issue is that since you're probably going to want to minify your javascript down to a single file, each new file that's added to the project also needs to be manually added to a Gulp or Grunt script to ensure that its included in the correct order. When you're busy coding your app, forgetting to do this is easy, and will create obvious problems if the concatenation and minification script is run with references missing.

The solution – RequireJS!

Enter RequireJS... This is something I swore by when doing Backbone development, and provides an excellent way to define the dependencies of each unit of functionality or pseudo class, and lets Require worry about how to do the importing. Another thing to note is that Require also has an excellent optimizer that will bundle all your scripts and libraries into one minified file for deployment.

I read around quite a lot on this subject and found lots of conflicting opinions, and after lots of experimentation, came up with what I found to be a nice solution. (Note: This article assumes a familiarity with Require, so if you're new to it, take a look at the getting started guide and API documentation)

Overview

(Note: As part one of a series, this tutorial only gets us to the point of defining an application module – functionality such as services, controllers and directives will be covered in Part 2)

To define the main module of an application using Require and Angular three javascript files are required, main.js, boot.js and app.js

main.js – Configuration

The main file should define the paths to javascript libraries you're using in your project, any shimming that you need to do for non AMD modules and a hook through to the file that bootstraps your application.

require.config({

  // Define paths
  paths: {
    'domReady': '../bower_components/requirejs-domready/domReady',
    'angular': '../bower_components/angular/angular'
  },

  // Shim those pesky non-AMD libraries
  shim: {
    'angular': {
      exports: 'angular'
    }
  },

  deps: ['./boot']

});
boot.js – Kick start the application

Here we bootstrap the Angular application to the html node of your index file.

define([  
  'require',
  'angular',
  'app'
], function (
  require,
  angular,
  app
) {
  'use strict';

  require(['domReady!'], function (document) {
    angular.bootstrap(document, ['app']);
  });

});
app.js – Define your application's main module

The app file specifies the first module of the application to be initialized, not particularly useful as it is, but more on that in part 2.

define([  
  'angular'
], function (
angular  
) {
  'use strict';

  return angular.module('app', [])
});
index.html

The script tag below specifies its src attribute as the path to the Require library and the data-main attribute as the main.js file discussed above. Note that this setup relies on your main file being at the root of the folder containing the javascript files for your application.

<script  
  type="text/javascript"
  src="<PATH_TO_LIBRARY_FILES>/requirejs/require.js"
  data-main="src/main.js">
</script>  
Where we're at codewise

So, currently we're able to define a module, which for simple applications will specify its own services, controllers and directives, but in more complex applications will include other other modules that in turn define their own sophisticated and granular functionality.

Compilation/Optimization, deployment, and Gulp

The index file shown above specifies main.js as the start point of the application, which is great for development and debugging, as each JavaScript file is pulled in separately, and the location of errors that pop up in the console can be easily traced back to their source. But for a large deployed application, loading in what maybe several hundred separate source files, is not really an option as the opening and closing of that many HTTP connections will mean that load times will be tiresome to say the least.

Fortunately RequireJS has as answer to this in the form of the optimizer. This command line tool facilitates the bundling of all the application's JavasScript into one minified file.

A simple way to include this in your workflow is through the Gulp streaming build system.

[Note: Since this is a post about how to set up Angular with Require I won't go into detail about the benefits of Gulp, but its a great, relatively new alternative to Grunt that provides stream based processing of files.]

var gulp = require('gulp');  
var rjs = require('gulp-requirejs');

// ————————————————–
// REQUIRE JS

gulp.task('requirejs', function() {  
  rjs({
    name: 'main',
    baseUrl: 'app/src/',
    out: 'main-min.js',
    mainConfigFile: 'app/src/main.js',
    shim: {}
  })
  .pipe(gulp.dest('./app/src'));
});

This task will create a main-min.js file in the root of the src folder which should be referenced in your index file when running in production mode

<script  
  type="text/javascript"
  src="<PATH_TO_LIBRARY_FILES>/requirejs/require.js"
  data-main="src/main-min.js">
</script>

Next steps

Hopefully this has given you a grounding in how to get started, and if you're hungry to learn more about Angular architecture and module structuring under Require, check out part two.

Source files

Take a look at the source files to see it in action.

Note that you'll need to navigate to the folder in a terminal app and run npm install and bower install. I've also included a very simple express server that can started by running node server/server.js from the terminal.