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

Commit 06ecb4b

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 693021c commit 06ecb4b

File tree

2 files changed

+37
-21
lines changed

2 files changed

+37
-21
lines changed

src/auto/injector.js

+27-21
Original file line numberDiff line numberDiff line change
@@ -801,48 +801,54 @@ function createInjector(modulesToLoad, strictDi) {
801801
}
802802
}
803803

804-
function invoke(fn, self, locals, serviceName) {
805-
if (typeof locals === 'string') {
806-
serviceName = locals;
807-
locals = null;
808-
}
809804

805+
function injectionArgs(fn, locals, serviceName) {
810806
var args = [],
811-
$inject = createInjector.$$annotate(fn, strictDi, serviceName),
812-
length, i,
813-
key;
807+
$inject = createInjector.$$annotate(fn, strictDi, serviceName);
814808

815-
for (i = 0, length = $inject.length; i < length; i++) {
816-
key = $inject[i];
809+
for (var i = 0, length = $inject.length; i < length; i++) {
810+
var key = $inject[i];
817811
if (typeof key !== 'string') {
818812
throw $injectorMinErr('itkn',
819813
'Incorrect injection token! Expected service name as string, got {0}', key);
820814
}
821-
args.push(
822-
locals && locals.hasOwnProperty(key)
823-
? locals[key]
824-
: getService(key, serviceName)
825-
);
815+
args.push(locals && locals.hasOwnProperty(key) ? locals[key] :
816+
getService(key, serviceName));
826817
}
818+
return args;
819+
}
820+
821+
822+
function invoke(fn, self, locals, serviceName) {
823+
if (typeof locals === 'string') {
824+
serviceName = locals;
825+
locals = null;
826+
}
827+
828+
var args = injectionArgs(fn, locals, serviceName);
827829
if (isArray(fn)) {
828-
fn = fn[length];
830+
fn = fn[fn.length - 1];
829831
}
830832

831833
// http://jsperf.com/angularjs-invoke-apply-vs-switch
832834
// #5388
833835
return fn.apply(self, args);
834836
}
835837

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

851+
846852
return {
847853
invoke: invoke,
848854
instantiate: instantiate,

test/auto/injectorSpec.js

+10
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,16 @@ describe('injector', function() {
270270
it('should take args before first arrow', function() {
271271
expect(annotate(eval('a => b => b'))).toEqual(['a']);
272272
});
273+
274+
it('should be possible to instantiate ES6 classes', function() {
275+
// Only Chrome (not even the FF we use) supports ES6 classes.
276+
if (!/chrome/i.test(navigator.userAgent)) return;
277+
providers('a', function() { return 'a-value'; });
278+
var clazz = eval('(class { constructor(a) { this.a = a; } aVal() { return this.a; } })');
279+
var instance = injector.instantiate(clazz);
280+
expect(instance).toEqual({a: 'a-value'});
281+
expect(instance.aVal()).toEqual('a-value');
282+
});
273283
/*jshint +W061 */
274284
});
275285
}

0 commit comments

Comments
 (0)