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

Commit e0567ce

Browse files
committed
feat($injector): support instantiating classes.
ES6's `class Foo {}` constructors cannot be instantiated using `fn.apply`. This change extracts injection argument collection and then uses new (Function.bind.apply(ctor, args)) to instantiate the service instance.
1 parent 1d18e60 commit e0567ce

File tree

2 files changed

+44
-26
lines changed

2 files changed

+44
-26
lines changed

src/auto/injector.js

+27-21
Original file line numberDiff line numberDiff line change
@@ -795,48 +795,54 @@ function createInjector(modulesToLoad, strictDi) {
795795
}
796796
}
797797

798-
function invoke(fn, self, locals, serviceName) {
799-
if (typeof locals === 'string') {
800-
serviceName = locals;
801-
locals = null;
802-
}
803798

799+
function injectionArgs(fn, locals, serviceName) {
804800
var args = [],
805-
$inject = createInjector.$$annotate(fn, strictDi, serviceName),
806-
length, i,
807-
key;
801+
$inject = createInjector.$$annotate(fn, strictDi, serviceName);
808802

809-
for (i = 0, length = $inject.length; i < length; i++) {
810-
key = $inject[i];
803+
for (var i = 0, length = $inject.length; i < length; i++) {
804+
var key = $inject[i];
811805
if (typeof key !== 'string') {
812806
throw $injectorMinErr('itkn',
813807
'Incorrect injection token! Expected service name as string, got {0}', key);
814808
}
815-
args.push(
816-
locals && locals.hasOwnProperty(key)
817-
? locals[key]
818-
: getService(key, serviceName)
819-
);
809+
args.push(locals && locals.hasOwnProperty(key) ? locals[key] :
810+
getService(key, serviceName));
820811
}
812+
return args;
813+
}
814+
815+
816+
function invoke(fn, self, locals, serviceName) {
817+
if (typeof locals === 'string') {
818+
serviceName = locals;
819+
locals = null;
820+
}
821+
822+
var args = injectionArgs(fn, locals, serviceName);
821823
if (isArray(fn)) {
822-
fn = fn[length];
824+
fn = fn[fn.length - 1];
823825
}
824826

825827
// http://jsperf.com/angularjs-invoke-apply-vs-switch
826828
// #5388
827829
return fn.apply(self, args);
828830
}
829831

832+
830833
function instantiate(Type, locals, serviceName) {
831834
// Check if Type is annotated and use just the given function at n-1 as parameter
832835
// e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
833-
// Object creation: http://jsperf.com/create-constructor/2
834-
var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null);
835-
var returnedValue = invoke(Type, instance, locals, serviceName);
836-
837-
return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
836+
var ctor = (isArray(Type) ? Type[Type.length - 1] : Type);
837+
var args = injectionArgs(Type, locals, serviceName);
838+
// Empty object at position 0 is ignored for invocation with `new`, but required.
839+
args.unshift({});
840+
/*jshint -W058 */ // Applying a constructor without immediate parentheses is the point here.
841+
return new (Function.prototype.bind.apply(ctor, args));
842+
/*jshint +W058 */
838843
}
839844

845+
840846
return {
841847
invoke: invoke,
842848
instantiate: instantiate,

test/auto/injectorSpec.js

+17-5
Original file line numberDiff line numberDiff line change
@@ -245,12 +245,24 @@ describe('injector', function() {
245245

246246
// Only Chrome and Firefox support this syntax.
247247
if (/chrome|firefox/i.test(navigator.userAgent)) {
248-
it('should be possible to annotate functions that are declared using ES6 syntax', function() {
249-
/*jshint -W061 */
250-
// The function is generated using `eval` as just having the ES6 syntax can break some browsers.
251-
expect(annotate(eval('({ fn(x) { return; } })').fn)).toEqual(['x']);
252-
/*jshint +W061 */
248+
/*jshint -W061 */
249+
describe('ES6 syntax', function() {
250+
// Functions and classes are generated using `eval` as just having the ES6 syntax can break
251+
// some browsers.
252+
it('should be possible to annotate ES6 methods',
253+
function() { expect(annotate(eval('({ fn(x) { return; } })').fn)).toEqual(['x']); });
254+
255+
it('should be possible to instantiate ES6 classes', function() {
256+
// Only Chrome (not even the FF we use) supports ES6 classes.
257+
if (!/chrome/i.test(navigator.userAgent)) return;
258+
providers('a', function() { return 'a-value'; });
259+
var clazz = eval('(class { constructor(a) { this.a = a; } aVal() { return this.a; } })');
260+
var instance = injector.instantiate(clazz);
261+
expect(instance).toEqual({a: 'a-value'});
262+
expect(instance.aVal()).toEqual('a-value');
263+
});
253264
});
265+
/*jshint +W061 */
254266
}
255267

256268

0 commit comments

Comments
 (0)