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.
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.
There is some boilerplate code involved.
The modules still name themselves instead of the naming happening at import.
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.
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.