Requiring vs Browserifying Angular

One of the aspects of Angular that seems to appeal to a multitude of people is its opinions on how you structure an application. Usually we consider opinions to be bad, since developers don’t want your ideas on what constitutes “correct” application architecture thrust upon them.

In the case of JavaScript, it seems that there was a mass of folks waiting for someone – anyone – to have a strong opinion on which enterprises could standardize and applications could be built, scaled and maintained by large and ever changing teams. In the end, we needed more than a foundation, we needed building plans.

blueprints

Angular’s Blueprint For Applications

The blueprint Angular offers is fundamentally quite simple – JavaScript doesn’t have a module system, so Angular provides one for you. Angular ensures that all of your JavaScript code is ready, loaded and available when your application runs. It does this primarily via dependency injection.

Consider a hypothetical, super simple application. There is one partial view. It has a corresponding controller. This controller in turn has a service injected into it for data access. Whenever the application runs, Angular makes sure that all of these “string” representations of actual modules are injected as objects.

// using Angular Kendo UI for UI components and data layer abstraction
(function () {

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

  // the routeProvider is injected here (Requires Angular.Route)
  app.config(['$routeProvider', function ($routeProvider) {
    $routeProvider.when('/home',
    {
      templateUrl: 'partials/home.html',
      controller: 'HomeController'
    })
    .otherwise(
    {
      redirectTo: '/home'
    })
  }]);

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

    $scope.title = 'Home';
    $scope.productsDataSource = $productsDataSource;

    $scope.listViewTemplate = '<p>{{ ShipCity }}</p>';

  }]);

  app.factory('productsDataSource', function () {
    new kendo.data.DataSource({
      type: 'odata',
      transport: {
        read: 'http://demos.telerik.com/kendo-ui/service/Northwind.svc/Orders'
      },
      pageSize: 20,
      serverPaging: true
    });
  });

}());

There is a lot going on here:

  • Declare the application module;
  • Create a factory which returns a Kendo UI DataSource;
  • Create controllers for partials injecting the DataSource into HomeCon.troller;
  • Define routes and match partials with controllers

The brilliant thing about Angular is that it mostly doesn’t matter in what order you do these things.

As long as the first app module exists, you can create any of the subsequent factories, controllers, routes or any of the rest in any order. Angular is then smart enough to look at your dependencies and load them for you, even if you specified the dependency after the dependent module. If you have been writing JavaScript for any amount of time, you know what a huge problem this solves.

Application Structure vs Physical Project Structure

At this point it at least appears as though we can create an application with some actual sanity in JavaScript. However, this app is already pretty verbose, and it does virtually nothing. Can you imagine what our file would look like in a real world app? Yikes!

The next logical step would be to break these controllers, services, and anything else we can out into separate files. This would be the physical project structure that mimics the coded one. We generally have two options here – Browserify and RequireJS

Browserifying Angular

That “app” object is really the key to everything that Angular is going to be doing. In normal usage, Angular assumes that the document will be ready by the time the application is “bootstrapped”. According to the documentation, Angular does “automatic initialization” on the DOMContentLoaded event.

It also says, “or when the angular.js script is evaluated if at that time document.readyState is set to complete“. Is it just me, or does that last sentence make zero sense? In any event, the steps Angular typically goes through whenever the DOM is ready are:

  • loads the module specified by the ng-app attribute;
  • creates the application injector – which is that thing which injects objects into other objects based on their string value;
  • compiles the HTML using whatever element contains the ng-app attribute as the root of the application and reads down the DOM tree from there.

This is how Angular is normally used. As long as all our scripts are loaded before DOMContentLoaded (think of this as document.ready), everything will be good. This makes Browserify a great solution for breaking Angular apps out into different physical files.

Using the above example, we could break down the files into the following structure…

  • app
    • partials
      • home.html
    • controllers
      • homeController.js
    • services
      • productsDataSource.js
    • app.js

Browserify allows the use of CommonJS modules in the browser. That means that each “module” needs to export itself so that it can be required by the others.

The homeController.js file would be:

// controllers/homeController.js

module.exports = function() {

  return function ($scope, $productsDataSource) {
   
    $scope.title = 'Home';
    $scope.productsDataSource = $productsDataSource;

   $scope.listViewTemplate = '<p>#: ShipCity #</p>';
  };

};

The productsDataSource.js factory is similarly simple:

// services/productsDataSource.js

module.exports = function () {
  // the productsDataSource service is injected into the controller
  return new kendo.data.DataSource({
    type: 'odata',
    transport: {
      read: 'http://demos.telerik.com/kendo-ui/service/Northwind.svc/Orders'
    },
    pageSize: 20,
    serverPaging: true
  });
};

The app.js file is where all the magic happens:

// app.js

// require all of the core libraries
require('../vendor/jquery/jquery.min');
require('../vendor/angular/angular.min');
require('../vendor/angular-route/angular-route.min');
require('../vendor/kendo-ui-core/js/kendo.ui.core.min');
require('../vendor/angular-kendo/angular-kendo');

// pull in the modules we are going to need (controllers, services, whatever)
var homeController = require('./controllers/homeController');
var productsDataSource = require('./services/productsDataSource');

// module up
var app = angular.module('app', [ 'ngRoute', 'kendo.directives' ]);

// routes and such
app.config(['$routeProvider', function($routeProvider) {
  $routeProvider
    .when('/home',
    {
      templateUrl: 'partials/home.html',
      controller: 'HomeController'
    })
    .otherwise(
    {
      redirectTo: '/home'
    });
}]);

// create factories
app.factory('productsDataSource', productsDataSource);

// create controllers
app.controller('HomeController', ['$scope', 'productsDataSource', homeController]);

And then, with all the command line skill in the world…

$> watchify js/app/**/*.js -o build/main.js

Watchify is a little utility which watches directories and “browserifys” all your code. I’ve taken some liberties here in assuming that you already have at least an awareness of browserify and what it is/does.

Some of this I like, and some of it makes me want to change my major.

I love how you can just require in vendor libraries in the app.js file. Beyond that, Browserify respects the order in which you require them in. Amazing.

I loathe the fact that I’m still manually creating controllers, factories and what not in the app.js file. It seems like I should be able to do this in the modules and pull them in. As it is, all my “Angular” code is really in the app.js file and every other file is just JavaScript. Well, it’s all just JavaScript so maybe I should shut up about it.

All in all, I like how Angular works with Browserify. I’m going to go out on a limb and say that Angular works pretty seamlessly with Browserify and I enjoyed working with it.

Next lets talk about something that I very much did not enjoy; RequireJS and Angular.

angrycat

OMG

I love RequireJS. I have written about it a bit, and use it in virtually all of my projects, both web and hybrid. I prefer it to Browserify. I believe, in my most humble of developer opinions, that RequireJS is the best way to module.

However…

Working with RequireJS and AngularJS was a vacation on Shutter Island. On the surface everything looks very normal. Under that surface is Ben Kingsley and a series of horrific flashbacks.

The issue at the core of this whole debacle is that Angular is doing things on DOM ready and doesn’t want to play your async games. Since RequireJS is all about async (AMD = Asynchronous Module Definition), reality begins to crumble around you as you try to put the pieces together.

Requiring Angular

Due to the async loading, the whole ng-app attribute is out. You cannot use it to specify your Angular app. This really tripped me up because it was the only way I knew how to Angular.

The second thing that is an issue is that darn app module. You can’t pass it around very easily without creating some crazy circular dependencies. This is an area of RequireJS that you want no part of.

There are plenty of blog posts on how to use Angular with RequireJS, but half of them I found to be incomplete and the other half looked like way more work than I wanted to do. What I ended up going with was something put together by Dmitry Eseev. I found his solution to be the most scalable and required the least amount of setup.

Based on his article, I came up with the following structure for the application…

  • app
    • partials
      • home.html
    • controllers
      • index.js
      • module.js
      • homeController.js
    • services
      • index.js
      • modules.js
      • productsDataSource.js
    • app.js
    • main.js
    • routes.js

Let’s start with the main.js file which requires in all vendor libraries (Angular, Kendo UI, jQuery) and shim’s the main app module. All of this is simply to make sure that the right files are loaded and executed in the right order.

require.config({
  paths: {
    'jquery': 'vendor/jquery/jquery',
    'angular': 'vendor/angular/angular',
    'kendo': 'vendor/kendo/kendo',
    'angular-kendo': 'vendor/angular-kendo',
    'app': 'app'
  },
  shim: {
    // make sure that kendo loads before angular-kendo
    'angular-kendo': ['kendo'],
    // make sure that 
    'app': {
        deps: ['jquery', 'angular', 'kendo', 'angular-kendo']
    }
  }
});

define(['routes'], function () {

  // create an angular application using the bootstrap method
  angular.bootstrap(document, ['app']);

});

Notice that the application is manually bootstrapped here. What this file is basically saying is, “load all of these files, then run angular on the document with ng-app set to ‘app'”. Since this file is loaded asynchronously by RequireJS, we have to use this “manual bootstrap” method to start the Angular application.

By the time that angular.bootstrap method is reached, all of the files have already been loaded. How does that happen? All via dependencies resolved by RequireJS. Notice above that the define function is asking for the routes.js file. RequireJS then loads this file before executing the angular.bootstrap method.

// routes.js

define([
  './app'
], function (app) {

  // app is the angular application object
  return app.config(['$routeProvider', function ($routeProvider) {
    $routeProvider
      .when('/home',
        {
          templateUrl: '/app/partials/home.html',
          controller: 'homeController'
        })
      .otherwise(
        {
          redirectTo: '/home'
        });
    
  }]);
});

The routes.js file has declared that app.js is a dependency. The app.js file create the angular application object and exposes it so that the routes can be defined off of it.

// app.js

define([
  './controllers/index',
  './services/index'
], function (controllers, index) {

  // the actual angular application module, passing
  // in all other modules needed for the application
  return angular.module('app', [
    'ngRoute',
    'kendo.directives',
    'app.controllers',
    'app.services'
  ]);
});

The app.js file creates the module and injects all of the required dependencies. This includes the ngRoute service, the Angular Kendo UI Directives and two other modules that we have yet to see, but were defined as dependencies in the top of the file. Those are the controllers/index.js file and the services/index.js file. Let’s break down the “controllers/index.js” file.

// controllers/index.js

define([
  './homeController'
], function () {
    
});

That code does nothing besides load dependencies. There is only one currently, but a larger application could and will have many, many controllers. All of those controllers would be loaded in this file. Each controller is then contained in a separate file.

// controllers/homeController.js

define([
  './module'
], function (module) {

  module.controller('homeController', ['$scope', '$productsDataSource',
    function ($scope, $productsDataSource) {
      $scope.title = 'Home';
      $scope.productsDataSource = $productsDataSource;

      $scope.listViewTemplate = '<p>#: ShipCity #</p>';
    };
  );

});

That’s the same old HomeController code, but it requires a module.js file. Another file?! Yep – last one for controllers. Its sole job is to create the app.controllers module so that it’s available when we try and create a controller off of it in any controller file.

// controllers/module.js

define([
], function () {

  return angular.module('app.controllers', []);

});

Let’s recap what just happened since that was pretty intense.

  • “main.js” requires “routes.js”
    • “routes.js” requires “app.js”
      • “app.js” requires “controllers/index.js”
        • “controllers/index.js” requires all controllers
          • all controllers require “module.js”
            • “module.js” creates the “app.controllers” module

That’s kind of a hairy dependency tree, but it scales really well. If you add a new controller, you just add the “controllers/nameController.js” file and add that same dependency to the “controllers/index.js” file.

The services work the same exact way. The app.js module requires the services/index.js file which requires all services. All services each require the services/module.js file which simply creates and provides the app.services module.

Back in the app.js file, all of these items are loaded in and passed to the Angular application module that we created. The very last thing that happens is that angular.bootstrap statement in the main.js file. Basically, we started at the top and worked our way to the bottom.

It’s far from ideal though.

RequireJS is forced to load all of the application code before the application ever runs. That means no lazy loading of code. Of course, you could make the argument that you should be using r.js to build all of your code into one file anyway, but you are still forcing the browser to load and parse every single bit of your code. I would consider that a micro-optimization though. If you find yourself with a bottleneck caused by JavaScript parsing, you may have just written Gmail, and you’ve got much bigger problems than how to structure your modules.

Browserify Or Require Or ?

I’ve already professed my preference for Require in most situations, but I actually believe that Browserify is better for AngularJS applications; if nothing else because you get to remove the async component, which really drops several levels of complexity.

Browserify and RequireJS are not the only module loaders on the planet. There are several others that are up and coming and worth looking into. I’ve recently heard great things about WebPack, which apparently not only works with AMD and CommonJS, but also any assets that might be going from the server to the client. It also handles pre-processors like LESS, CoffeeScript, Jade and others.

What module loader do you use with AngularJS? Have an opinion about Browserify vs Require? What about the Angular Seed Project? There are lots of options out there and I would love to know what everyone else is doing to get a structure that is as sexy and robust as Angular itself is.

Blueprint photo by Will Scullin

Cat photo titled “Angry Tiger” by Guyon More√©

Comments