Skip to content

Commit 7d4aee3

Browse files
committed
Auto create $inject property form the argument names. Any arg starting with $ or _ will be injected
1 parent 7a54d27 commit 7d4aee3

4 files changed

Lines changed: 110 additions & 23 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
### API
55
- rewrite of JQuery lite implementation for better supports operations on multiple nodes when
66
matched by a selector.
7+
- Infer DI dependencies from function signature. http://docs.angularjs.org/#!guide.di
78

89
### Breaking changes
910
- Removed the $init() method after the compilation. The old way of compiling the DOM element was

src/Angular.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1077,10 +1077,15 @@ function bindJQuery(){
10771077
/**
10781078
* throw error of the argument is falsy.
10791079
*/
1080-
function assertArg(arg, name) {
1080+
function assertArg(arg, name, reason) {
10811081
if (!arg) {
1082-
var error = new Error("Argument '" + name + "' is required");
1082+
var error = new Error("Argument '" + (name||'?') + "' is " +
1083+
(reason || "required"));
10831084
if (window.console) window.console.log(error.stack);
10841085
throw error;
10851086
}
10861087
};
1088+
1089+
function assertArgFn(arg, name) {
1090+
assertArg(isFunction(arg, name, 'not a function'));
1091+
};

src/Injector.js

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,37 +5,41 @@
55
*
66
* @description
77
* Creates an inject function that can be used for dependency injection.
8+
* (See {@link guide.di dependency injection})
9+
*
10+
* The inject function can be used for retrieving service instances or for calling any function
11+
* which has the $inject property so that the services can be automatically provided. Angular
12+
* creates an injection function automatically for the root scope and it is available as
13+
* {@link angular.scope.$service $service}.
814
*
915
* @param {Object=} [providerScope={}] provider's `this`
1016
* @param {Object.<string, function()>=} [providers=angular.service] Map of provider (factory)
1117
* function.
1218
* @param {Object.<string, function()>=} [cache={}] Place where instances are saved for reuse. Can
1319
* also be used to override services speciafied by `providers` (useful in tests).
14-
* @returns {function()} Injector function.
20+
* @returns
21+
* {function()} Injector function: `function(value, scope, args...)`:
22+
*
23+
* * `value` - `{string|array|function}`
24+
* * `scope(optional=rootScope)` - optional function "`this`" when `value` is type `function`.
25+
* * `args(optional)` - optional set of arguments to pass to function after injection arguments.
26+
* (also known as curry arguments or currying).
27+
*
28+
* #Return value of `function(value, scope, args...)`
29+
* The injector function return value depended on the type of `value` argument:
30+
*
31+
* * `string`: return an instance for the injection key.
32+
* * `array` of keys: returns an array of instances for those keys. (see `string` above.)
33+
* * `function`: look at `$inject` property of function to determine instances to inject
34+
* and then call the function with instances and `scope`. Any additional arguments
35+
* (`args`) are appended to the function arguments.
36+
* * `none`: initialize eager providers.
1537
*
16-
* @TODO These docs need a lot of work. Specifically the returned function should be described in
17-
* great detail + we need to provide some examples.
1838
*/
1939
function createInjector(providerScope, providers, cache) {
2040
providers = providers || angularService;
2141
cache = cache || {};
2242
providerScope = providerScope || {};
23-
/**
24-
* injection function
25-
* @param value: string, array, object or function.
26-
* @param scope: optional function "this"
27-
* @param args: optional arguments to pass to function after injection
28-
* parameters
29-
* @returns depends on value:
30-
* string: return an instance for the injection key.
31-
* array of keys: returns an array of instances.
32-
* function: look at $inject property of function to determine instances
33-
* and then call the function with instances and `scope`. Any
34-
* additional arguments (`args`) are appended to the function
35-
* arguments.
36-
* object: initialize eager providers and publish them the ones with publish here.
37-
* none: same as object but use providerScope as place to publish.
38-
*/
3943
return function inject(value, scope, args){
4044
var returnValue, provider;
4145
if (isString(value)) {
@@ -51,7 +55,7 @@ function createInjector(providerScope, providers, cache) {
5155
returnValue.push(inject(name));
5256
});
5357
} else if (isFunction(value)) {
54-
returnValue = inject(value.$inject || []);
58+
returnValue = inject(injectionArgs(value));
5559
returnValue = value.apply(scope, concat(returnValue, arguments, 2));
5660
} else if (isObject(value)) {
5761
forEach(providers, function(provider, name){
@@ -80,3 +84,33 @@ function injectUpdateView(fn) {
8084
function angularServiceInject(name, fn, inject, eager) {
8185
angularService(name, fn, {$inject:inject, $eager:eager});
8286
}
87+
88+
89+
/**
90+
* @returns the $inject property of function. If not found the
91+
* the $inject is computed by looking at the toString of function and
92+
* extracting all arguments which start with $ or end with _ as the
93+
* injection names.
94+
*/
95+
var FN_ARGS = /^function [^\(]*\(([^\)]*)\)/;
96+
var FN_ARG_SPLIT = /,/;
97+
var FN_ARG = /^\s*(((\$?).+?)(_?))\s*$/;
98+
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
99+
function injectionArgs(fn) {
100+
assertArgFn(fn);
101+
if (!fn.$inject) {
102+
var args = fn.$inject = [];
103+
var fnText = fn.toString().replace(STRIP_COMMENTS, '');
104+
var argDecl = fnText.match(FN_ARGS);
105+
forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
106+
arg.replace(FN_ARG, function(all, name, injectName, $, _){
107+
assertArg(args, name, 'after non-injectable arg');
108+
if ($ || _)
109+
args.push(injectName);
110+
else
111+
args = null; // once we reach an argument which is not injectable then ignore
112+
});
113+
});
114+
}
115+
return fn.$inject;
116+
};

test/InjectorSpec.js

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,56 @@ describe('injector', function(){
5353

5454
it('should autostart eager services', function(){
5555
var log = '';
56-
providers('eager', function(){log += 'eager;'; return 'foo'}, {$eager: true});
56+
providers('eager', function(){log += 'eager;'; return 'foo';}, {$eager: true});
5757
inject();
5858
expect(log).toEqual('eager;');
5959
expect(inject('eager')).toBe('foo');
6060
});
61+
62+
describe('annotation', function(){
63+
it('should return $inject', function(){
64+
function fn(){};
65+
fn.$inject = ['a'];
66+
expect(injectionArgs(fn)).toBe(fn.$inject);
67+
expect(injectionArgs(function(){})).toEqual([]);
68+
});
69+
70+
it('should create $inject', function(){
71+
// keep the multi-line to make sure we can handle it
72+
function $f_n0(
73+
$a, // x, <-- looks like an arg but it is a comment
74+
b_, /* z, <-- looks like an arg but it is a
75+
multi-line comment
76+
function (a, b){}
77+
*/
78+
/* {some type} */ c){ extraParans();};
79+
expect(injectionArgs($f_n0)).toEqual(['$a', 'b']);
80+
expect($f_n0.$inject).toEqual(['$a', 'b']);
81+
});
82+
83+
it('should handle no arg functions', function(){
84+
function $f_n0(){};
85+
expect(injectionArgs($f_n0)).toEqual([]);
86+
expect($f_n0.$inject).toEqual([]);
87+
});
88+
89+
it('should handle args with both $ and _', function(){
90+
function $f_n0($a_){};
91+
expect(injectionArgs($f_n0)).toEqual(['$a']);
92+
expect($f_n0.$inject).toEqual(['$a']);
93+
});
94+
95+
it('should throw on non function arg', function(){
96+
expect(function(){
97+
injectionArgs({});
98+
}).toThrow();
99+
});
100+
101+
it('should throw on injectable after non-injectable arg', function(){
102+
expect(function(){
103+
injectionArgs(function($a, b_, nonInject, d_){});
104+
}).toThrow();
105+
});
106+
107+
});
61108
});

0 commit comments

Comments
 (0)