Skip to content

Instantly share code, notes, and snippets.

@spion
Last active February 28, 2016 05:26
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save spion/5446337 to your computer and use it in GitHub Desktop.
Save spion/5446337 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.
@dfernandez79
Copy link

Maybe this is for an old version of Angular, or there is something that I'm missing. Right now I use Angular with browserify by just doing:

  $routeProvider.when('/path', {templateUrl: '/file.html', controller: 'ControllerName' });

You can use a controller name instead of passing an instance.

Then in the module configuration:

  var controllers = require('./controllers');

  app.controller('ControllerName', controllers.ControllerName);

On the controllers directory I put a index.js file that does:

   module.exports = { ControllerName: require('./ControllerName.js'); }

On ControllerName.js, I use the $inject function property so Angular DI works:

  function Controller($scope) {

  }
  Controller.$inject = ['$scope'];
  module.exports = Controller;

This approach is working for me on Angular 1.2.0rc2

@spion
Copy link
Author

spion commented Oct 18, 2013

The problem with your approach is that if you remove the controller later on (refactored, stopped using, created a different page), you'll have to remember to update index.js and remove it there aswell or the bundle will keep including it.

With my approach, once you remove the controller from all places where it appears in the router it becomes a non-dependency and therefore its not included in the bundle.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment