angular.js/src/Injector.js

145 lines
4.8 KiB
JavaScript

/**
* @ngdoc function
* @name angular.injector
* @function
*
* @description
* Creates an injector function that can be used for retrieving services as well as for
* dependency injection (see {@link guide/dev_guide.di dependency injection}).
*
* Angular creates an injector automatically for the root scope and it is available as the
* {@link angular.scope.$service $service} property. Creation of the injector automatically creates
* all of the `$eager` {@link angular.service services}.
*
* @param {Object=} [factoryScope={}] `this` for the service factory function.
* @param {Object.<string, function()>=} [factories=angular.service] Map of service factory
* functions.
* @param {Object.<string, function()>=} [instanceCache={}] Place where instances of services are
* saved for reuse. Can also be used to override services specified by `serviceFactory`
* (useful in tests).
* @returns {function()} Injector function:
*
* * `injector(serviceName)`:
* * `serviceName` - `{string=}` - name of the service to retrieve.
*
* The injector function also has these properties:
*
* * an `invoke` property which can be used to invoke methods with dependency-injected arguments.
* `injector.invoke(self, fn, curryArgs)`
* * `self` - "`this`" to be used when invoking the function.
* * `fn` - the function to be invoked. The function may have the `$inject` property which
* lists the set of arguments which should be auto injected
* (see {@link guide.di dependency injection}).
* * `curryArgs(array)` - optional array of arguments to pass to function invocation after the
* injection arguments (also known as curry arguments or currying).
* * an `eager` property which is used to initialize the eager services.
* `injector.eager()`
*/
function createInjector(factoryScope, factories, instanceCache) {
factories = factories || angularService;
instanceCache = instanceCache || {};
factoryScope = factoryScope || {};
injector.invoke = invoke;
injector.eager = function(){
forEach(factories, function(factory, name){
if (factory.$eager)
injector(name);
if (factory.$creation)
throw new Error("Failed to register service '" + name +
"': $creation property is unsupported. Use $eager:true or see release notes.");
});
};
return injector;
function injector(value){
if (!(value in instanceCache)) {
var factory = factories[value];
if (!factory) throw Error("Unknown provider for '"+value+"'.");
instanceCache[value] = invoke(factoryScope, factory);
}
return instanceCache[value];
};
function invoke(self, fn, args){
args = args || [];
var injectNames = injectionArgs(fn);
var i = injectNames.length;
while(i--) {
args.unshift(injector(injectNames[i]));
}
return fn.apply(self, args);
}
}
/*NOT_PUBLIC_YET
* @ngdoc function
* @name angular.annotate
* @function
*
* @description
* Annotate the function with injection arguments. This is equivalent to setting the `$inject`
* property as described in {@link guide.di dependency injection}.
*
* <pre>
* var MyController = angular.annotate('$location', function($location){ ... });
* </pre>
*
* is the same as
*
* <pre>
* var MyController = function($location){ ... };
* MyController.$inject = ['$location'];
* </pre>
*
* @param {String|Array} serviceName... zero or more service names to inject into the
* `annotatedFunction`.
* @param {function} annotatedFunction function to annotate with `$inject`
* functions.
* @returns {function} `annotatedFunction`
*/
function annotate(services, fn) {
if (services instanceof Array) {
fn.$inject = services;
return fn;
} else {
var i = 0,
length = arguments.length - 1, // last one is the destination function
$inject = arguments[length].$inject = [];
for (; i < length; i++) {
$inject.push(arguments[i]);
}
return arguments[length]; // return the last one
}
}
function angularServiceInject(name, fn, inject, eager) {
angularService(name, fn, {$inject:inject, $eager:eager});
}
/**
* @returns the $inject property of function. If not found the
* the $inject is computed by looking at the toString of function and
* extracting all arguments which and assuming that they are the
* injection names.
*/
var FN_ARGS = /^function\s*[^\(]*\(([^\)]*)\)/;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(.+?)\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
function injectionArgs(fn) {
assertArgFn(fn);
if (!fn.$inject) {
var args = fn.$inject = [];
var fnText = fn.toString().replace(STRIP_COMMENTS, '');
var argDecl = fnText.match(FN_ARGS);
forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
arg.replace(FN_ARG, function(all, name){
args.push(name);
});
});
}
return fn.$inject;
}