Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 4361efb

Browse files
committed
feat($injector): provide API for retrieving function annotations
1 parent 416a783 commit 4361efb

File tree

2 files changed

+131
-38
lines changed

2 files changed

+131
-38
lines changed

src/auto/injector.js

+115-27
Original file line numberDiff line numberDiff line change
@@ -42,19 +42,32 @@ var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
4242
var FN_ARG_SPLIT = /,/;
4343
var FN_ARG = /^\s*(_?)(.+?)\1\s*$/;
4444
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
45-
function inferInjectionArgs(fn) {
46-
assertArgFn(fn);
47-
if (!fn.$inject) {
48-
var args = fn.$inject = [];
49-
var fnText = fn.toString().replace(STRIP_COMMENTS, '');
50-
var argDecl = fnText.match(FN_ARGS);
51-
forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
52-
arg.replace(FN_ARG, function(all, underscore, name){
53-
args.push(name);
45+
function annotate(fn) {
46+
var $inject,
47+
fnText,
48+
argDecl,
49+
last;
50+
51+
if (typeof fn == 'function') {
52+
if (!($inject = fn.$inject)) {
53+
$inject = [];
54+
fnText = fn.toString().replace(STRIP_COMMENTS, '');
55+
argDecl = fnText.match(FN_ARGS);
56+
forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
57+
arg.replace(FN_ARG, function(all, underscore, name){
58+
$inject.push(name);
59+
});
5460
});
55-
});
61+
fn.$inject = $inject;
62+
}
63+
} else if (isArray(fn)) {
64+
last = fn.length - 1;
65+
assertArgFn(fn[last], 'fn')
66+
$inject = fn.slice(0, last);
67+
} else {
68+
assertArgFn(fn, 'fn', true);
5669
}
57-
return fn.$inject;
70+
return $inject;
5871
}
5972

6073
///////////////////////////////////////
@@ -152,6 +165,87 @@ function inferInjectionArgs(fn) {
152165
* @returns {Object} new instance of `Type`.
153166
*/
154167

168+
/**
169+
* @ngdoc method
170+
* @name angular.module.AUTO.$injector#annotate
171+
* @methodOf angular.module.AUTO.$injector
172+
*
173+
* @description
174+
* Returns an array of service names which the function is requesting for injection. This API is used by the injector
175+
* to determine which services need to be injected into the function when the function is invoked. There are three
176+
* ways in which the function can be annotated with the needed dependencies.
177+
*
178+
* # Argument names
179+
*
180+
* The simplest form is to extract the dependencies from the arguments of the function. This is done by converting
181+
* the function into a string using `toString()` method and extracting the argument names.
182+
* <pre>
183+
* // Given
184+
* function MyController($scope, $route) {
185+
* // ...
186+
* }
187+
*
188+
* // Then
189+
* expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
190+
* </pre>
191+
*
192+
* This method does not work with code minfication / obfuscation. For this reason the following annotation strategies
193+
* are supported.
194+
*
195+
* # The `$injector` property
196+
*
197+
* If a function has an `$inject` property and its value is an array of strings, then the strings represent names of
198+
* services to be injected into the function.
199+
* <pre>
200+
* // Given
201+
* var MyController = function(obfuscatedScope, obfuscatedRoute) {
202+
* // ...
203+
* }
204+
* // Define function dependencies
205+
* MyController.$inject = ['$scope', '$route'];
206+
*
207+
* // Then
208+
* expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
209+
* </pre>
210+
*
211+
* # The array notation
212+
*
213+
* It is often desirable to inline Injected functions and that's when setting the `$inject` property is very
214+
* inconvenient. In these situations using the array notation to specify the dependencies in a way that survives
215+
* minification is a better choice:
216+
*
217+
* <pre>
218+
* // We wish to write this (not minification / obfuscation safe)
219+
* injector.invoke(function($compile, $rootScope) {
220+
* // ...
221+
* });
222+
*
223+
* // We are forced to write break inlining
224+
* var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) {
225+
* // ...
226+
* };
227+
* tmpFn.$inject = ['$compile', '$rootScope'];
228+
* injector.invoke(tempFn);
229+
*
230+
* // To better support inline function the inline annotation is supported
231+
* injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) {
232+
* // ...
233+
* }]);
234+
*
235+
* // Therefore
236+
* expect(injector.annotate(
237+
* ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}])
238+
* ).toEqual(['$compile', '$rootScope']);
239+
* </pre>
240+
*
241+
* @param {function|Array.<string|Function>} fn Function for which dependent service names need to be retrieved as described
242+
* above.
243+
*
244+
* @returns {Array.<string>} The names of the services which the function requires.
245+
*/
246+
247+
248+
155249

156250
/**
157251
* @ngdoc object
@@ -454,30 +548,23 @@ function createInjector(modulesToLoad) {
454548

455549
function invoke(fn, self, locals){
456550
var args = [],
457-
$inject,
458-
length,
551+
$inject = annotate(fn),
552+
length, i,
459553
key;
460554

461-
if (typeof fn == 'function') {
462-
$inject = inferInjectionArgs(fn);
463-
length = $inject.length;
464-
} else {
465-
if (isArray(fn)) {
466-
$inject = fn;
467-
length = $inject.length - 1;
468-
fn = $inject[length];
469-
}
470-
assertArgFn(fn, 'fn');
471-
}
472-
473-
for(var i = 0; i < length; i++) {
555+
for(i = 0, length = $inject.length; i < length; i++) {
474556
key = $inject[i];
475557
args.push(
476558
locals && locals.hasOwnProperty(key)
477559
? locals[key]
478560
: getService(key, path)
479561
);
480562
}
563+
if (!fn.$inject) {
564+
// this means that we must be an array.
565+
fn = fn[length];
566+
}
567+
481568

482569
// Performance optimization: http://jsperf.com/apply-vs-call-vs-invoke
483570
switch (self ? -1 : args.length) {
@@ -510,7 +597,8 @@ function createInjector(modulesToLoad) {
510597
return {
511598
invoke: invoke,
512599
instantiate: instantiate,
513-
get: getService
600+
get: getService,
601+
annotate: annotate
514602
};
515603
}
516604
}

test/auto/injectorSpec.js

+16-11
Original file line numberDiff line numberDiff line change
@@ -123,11 +123,11 @@ describe('injector', function() {
123123
it('should return $inject', function() {
124124
function fn() {}
125125
fn.$inject = ['a'];
126-
expect(inferInjectionArgs(fn)).toBe(fn.$inject);
127-
expect(inferInjectionArgs(function() {})).toEqual([]);
128-
expect(inferInjectionArgs(function () {})).toEqual([]);
129-
expect(inferInjectionArgs(function () {})).toEqual([]);
130-
expect(inferInjectionArgs(function /* */ () {})).toEqual([]);
126+
expect(annotate(fn)).toBe(fn.$inject);
127+
expect(annotate(function() {})).toEqual([]);
128+
expect(annotate(function () {})).toEqual([]);
129+
expect(annotate(function () {})).toEqual([]);
130+
expect(annotate(function /* */ () {})).toEqual([]);
131131
});
132132

133133

@@ -142,43 +142,48 @@ describe('injector', function() {
142142
*/
143143
_c,
144144
/* {some type} */ d) { extraParans();}
145-
expect(inferInjectionArgs($f_n0)).toEqual(['$a', 'b_', '_c', 'd']);
145+
expect(annotate($f_n0)).toEqual(['$a', 'b_', '_c', 'd']);
146146
expect($f_n0.$inject).toEqual(['$a', 'b_', '_c', 'd']);
147147
});
148148

149149

150150
it('should strip leading and trailing underscores from arg name during inference', function() {
151151
function beforeEachFn(_foo_) { /* foo = _foo_ */ };
152-
expect(inferInjectionArgs(beforeEachFn)).toEqual(['foo']);
152+
expect(annotate(beforeEachFn)).toEqual(['foo']);
153153
});
154154

155155

156156
it('should handle no arg functions', function() {
157157
function $f_n0() {}
158-
expect(inferInjectionArgs($f_n0)).toEqual([]);
158+
expect(annotate($f_n0)).toEqual([]);
159159
expect($f_n0.$inject).toEqual([]);
160160
});
161161

162162

163163
it('should handle no arg functions with spaces in the arguments list', function() {
164164
function fn( ) {}
165-
expect(inferInjectionArgs(fn)).toEqual([]);
165+
expect(annotate(fn)).toEqual([]);
166166
expect(fn.$inject).toEqual([]);
167167
});
168168

169169

170170
it('should handle args with both $ and _', function() {
171171
function $f_n0($a_) {}
172-
expect(inferInjectionArgs($f_n0)).toEqual(['$a_']);
172+
expect(annotate($f_n0)).toEqual(['$a_']);
173173
expect($f_n0.$inject).toEqual(['$a_']);
174174
});
175175

176176

177177
it('should throw on non function arg', function() {
178178
expect(function() {
179-
inferInjectionArgs({});
179+
annotate({});
180180
}).toThrow();
181181
});
182+
183+
184+
it('should publish annotate API', function() {
185+
expect(injector.annotate).toBe(annotate);
186+
});
182187
});
183188

184189

0 commit comments

Comments
 (0)