Skip to content

Instantly share code, notes, and snippets.

@TexRx
Forked from spion/angular-browserify.md
Created May 16, 2014 21:46
Show Gist options
  • Save TexRx/fae9f6380a55d76e3a8f to your computer and use it in GitHub Desktop.
Save TexRx/fae9f6380a55d76e3a8f to your computer and use it in GitHub Desktop.

How to use angular.js with browserify.

To define a factory, service, controller or a directive, use the following format:

exports.inject = function(app) {
    app.factory('MyFactory', exports.factory);
    return exports.factory;
}

exports.factory = function() {
    return {doStuff: function() {}};
};

Then to use this factory as a dependency to another browserify module (e.g. a controller):

exports.inject = function(app) {
    require('./path/to/my-factory').inject(app);
    app.controller('MyController', exports.controller);
    return exports.controller
};
exports.controller = function MyController$ng($scope, MyFactory) {
    MyFactory.doStuff();
};

Then you can specify this controller from routeProvider:

$routeProvider.when('/myroute', {
    templateUrl: '/site/files/index.html',
    controller: require('./path/to/MyController').inject(app)
});

Before minifying, use the ngbmin preprocessor: it transforms all function expressions that end in $ng to the array injection syntax. Unlike ngmin, ngbmin will always do the right thing without trying to solve the halting problem.

Upsides

Normally angular demands that you put all dependencies in the place where you define the module. This means that you have two options:

  • define all modules in the main module then manually check if they're still needed every time you remove a dependency anywhere, or

  • put everything in a separate module, which means duplicate dependency strings for every single entity.

But with this approach, dependencies are handled on-the-spot where they are needed, unlike raw angular. This means that if you remove all controllers (or perhaps routes) that depend on 'fooService', 'fooService' will also be removed from the resulting minified code, automatically.

It's testable, because you can just inject the exported controller or service manually in tests, or inject everything but replace certain things with others.

Downsides

There is some boilerplate code involved.

The modules still name themselves instead of the naming happening at import.

Alternative approach

Considered, but didn't implement:

module.exports = ngb.controller(function $ngb(inject) {
   var $scope = inject('$scope');
   var service = inject('./path/to/myService');
   var regularModule = require('regular-module');
});

becomes

module.exports = angular
  .module(__filename, [require.resolve('./path/to/myService')])
  .controller(__filename, ['inject', function(inject) {
        inject = inject(require);
        var scope = inject('$scope');
        var service = inject('./path/to/myService');
        var regularModule = require('regular-module');
  });

inject is a helper service containing a path function that returns an inject function that either injects by path, or failing that, attempts to inject by name:

        function inject(require) { 
            return function inject(name) {
                try { return $injector.get(require.resolve(name)); }
                catch (e) { return $injector.get(name); }
            }
        }

Why was this approach rejected?

  • Complex
  • The code will be coupled with the precompiler, which means it will be harder to backpedal if it turns out to be a bad idea.
  • Most importantly, angular also has places where injection is allowed but defining new modules is forbidden. Didn't know how to handle those in terms of finding the responsible module and adding the necessary dependencies there.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment