aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMisko Hevery2011-02-16 19:18:35 -0500
committerMisko Hevery2011-02-18 13:14:07 -0800
commit7d4aee31bb202e9b050fc15c56cfc33c46b9eaf5 (patch)
tree3c503f5ac9104983d3ed665da76a464142e94a53
parent7a54d2791ff4de5808970f3862114f84121115b5 (diff)
downloadangular.js-7d4aee31bb202e9b050fc15c56cfc33c46b9eaf5.tar.bz2
Auto create $inject property form the argument names. Any arg starting with $ or _ will be injected
-rw-r--r--CHANGELOG.md1
-rw-r--r--src/Angular.js9
-rw-r--r--src/Injector.js74
-rw-r--r--test/InjectorSpec.js49
4 files changed, 110 insertions, 23 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 57665b9a..ef08e2c5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
### API
- rewrite of JQuery lite implementation for better supports operations on multiple nodes when
matched by a selector.
+- Infer DI dependencies from function signature. http://docs.angularjs.org/#!guide.di
### Breaking changes
- Removed the $init() method after the compilation. The old way of compiling the DOM element was
diff --git a/src/Angular.js b/src/Angular.js
index 2d4b1671..05b9989e 100644
--- a/src/Angular.js
+++ b/src/Angular.js
@@ -1077,10 +1077,15 @@ function bindJQuery(){
/**
* throw error of the argument is falsy.
*/
-function assertArg(arg, name) {
+function assertArg(arg, name, reason) {
if (!arg) {
- var error = new Error("Argument '" + name + "' is required");
+ var error = new Error("Argument '" + (name||'?') + "' is " +
+ (reason || "required"));
if (window.console) window.console.log(error.stack);
throw error;
}
};
+
+function assertArgFn(arg, name) {
+ assertArg(isFunction(arg, name, 'not a function'));
+};
diff --git a/src/Injector.js b/src/Injector.js
index 295928bf..a5a0b540 100644
--- a/src/Injector.js
+++ b/src/Injector.js
@@ -5,37 +5,41 @@
*
* @description
* Creates an inject function that can be used for dependency injection.
+ * (See {@link guide.di dependency injection})
+ *
+ * The inject function can be used for retrieving service instances or for calling any function
+ * which has the $inject property so that the services can be automatically provided. Angular
+ * creates an injection function automatically for the root scope and it is available as
+ * {@link angular.scope.$service $service}.
*
* @param {Object=} [providerScope={}] provider's `this`
* @param {Object.<string, function()>=} [providers=angular.service] Map of provider (factory)
* function.
* @param {Object.<string, function()>=} [cache={}] Place where instances are saved for reuse. Can
* also be used to override services speciafied by `providers` (useful in tests).
- * @returns {function()} Injector function.
+ * @returns
+ * {function()} Injector function: `function(value, scope, args...)`:
+ *
+ * * `value` - `{string|array|function}`
+ * * `scope(optional=rootScope)` - optional function "`this`" when `value` is type `function`.
+ * * `args(optional)` - optional set of arguments to pass to function after injection arguments.
+ * (also known as curry arguments or currying).
+ *
+ * #Return value of `function(value, scope, args...)`
+ * The injector function return value depended on the type of `value` argument:
+ *
+ * * `string`: return an instance for the injection key.
+ * * `array` of keys: returns an array of instances for those keys. (see `string` above.)
+ * * `function`: look at `$inject` property of function to determine instances to inject
+ * and then call the function with instances and `scope`. Any additional arguments
+ * (`args`) are appended to the function arguments.
+ * * `none`: initialize eager providers.
*
- * @TODO These docs need a lot of work. Specifically the returned function should be described in
- * great detail + we need to provide some examples.
*/
function createInjector(providerScope, providers, cache) {
providers = providers || angularService;
cache = cache || {};
providerScope = providerScope || {};
- /**
- * injection function
- * @param value: string, array, object or function.
- * @param scope: optional function "this"
- * @param args: optional arguments to pass to function after injection
- * parameters
- * @returns depends on value:
- * string: return an instance for the injection key.
- * array of keys: returns an array of instances.
- * function: look at $inject property of function to determine instances
- * and then call the function with instances and `scope`. Any
- * additional arguments (`args`) are appended to the function
- * arguments.
- * object: initialize eager providers and publish them the ones with publish here.
- * none: same as object but use providerScope as place to publish.
- */
return function inject(value, scope, args){
var returnValue, provider;
if (isString(value)) {
@@ -51,7 +55,7 @@ function createInjector(providerScope, providers, cache) {
returnValue.push(inject(name));
});
} else if (isFunction(value)) {
- returnValue = inject(value.$inject || []);
+ returnValue = inject(injectionArgs(value));
returnValue = value.apply(scope, concat(returnValue, arguments, 2));
} else if (isObject(value)) {
forEach(providers, function(provider, name){
@@ -80,3 +84,33 @@ function injectUpdateView(fn) {
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 start with $ or end with _ as the
+ * injection names.
+ */
+var FN_ARGS = /^function [^\(]*\(([^\)]*)\)/;
+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, injectName, $, _){
+ assertArg(args, name, 'after non-injectable arg');
+ if ($ || _)
+ args.push(injectName);
+ else
+ args = null; // once we reach an argument which is not injectable then ignore
+ });
+ });
+ }
+ return fn.$inject;
+};
diff --git a/test/InjectorSpec.js b/test/InjectorSpec.js
index 765003cf..c876f188 100644
--- a/test/InjectorSpec.js
+++ b/test/InjectorSpec.js
@@ -53,9 +53,56 @@ describe('injector', function(){
it('should autostart eager services', function(){
var log = '';
- providers('eager', function(){log += 'eager;'; return 'foo'}, {$eager: true});
+ providers('eager', function(){log += 'eager;'; return 'foo';}, {$eager: true});
inject();
expect(log).toEqual('eager;');
expect(inject('eager')).toBe('foo');
});
+
+ describe('annotation', function(){
+ it('should return $inject', function(){
+ function fn(){};
+ fn.$inject = ['a'];
+ expect(injectionArgs(fn)).toBe(fn.$inject);
+ expect(injectionArgs(function(){})).toEqual([]);
+ });
+
+ it('should create $inject', function(){
+ // keep the multi-line to make sure we can handle it
+ function $f_n0(
+ $a, // x, <-- looks like an arg but it is a comment
+ b_, /* z, <-- looks like an arg but it is a
+ multi-line comment
+ function (a, b){}
+ */
+ /* {some type} */ c){ extraParans();};
+ expect(injectionArgs($f_n0)).toEqual(['$a', 'b']);
+ expect($f_n0.$inject).toEqual(['$a', 'b']);
+ });
+
+ it('should handle no arg functions', function(){
+ function $f_n0(){};
+ expect(injectionArgs($f_n0)).toEqual([]);
+ expect($f_n0.$inject).toEqual([]);
+ });
+
+ it('should handle args with both $ and _', function(){
+ function $f_n0($a_){};
+ expect(injectionArgs($f_n0)).toEqual(['$a']);
+ expect($f_n0.$inject).toEqual(['$a']);
+ });
+
+ it('should throw on non function arg', function(){
+ expect(function(){
+ injectionArgs({});
+ }).toThrow();
+ });
+
+ it('should throw on injectable after non-injectable arg', function(){
+ expect(function(){
+ injectionArgs(function($a, b_, nonInject, d_){});
+ }).toThrow();
+ });
+
+ });
});