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

Commit

Permalink
feat($injector): support instantiating classes.
Browse files Browse the repository at this point in the history
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.

Closes: #12598
Closes: #12597
  • Loading branch information
mprobst authored and lgalfaso committed Dec 10, 2015
1 parent 8f0b482 commit 8b6b428
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 21 deletions.
48 changes: 27 additions & 21 deletions src/auto/injector.js
Expand Up @@ -804,48 +804,54 @@ function createInjector(modulesToLoad, strictDi) {
}
}

function invoke(fn, self, locals, serviceName) {
if (typeof locals === 'string') {
serviceName = locals;
locals = null;
}

function injectionArgs(fn, locals, serviceName) {
var args = [],
$inject = createInjector.$$annotate(fn, strictDi, serviceName),
length, i,
key;
$inject = createInjector.$$annotate(fn, strictDi, serviceName);

for (i = 0, length = $inject.length; i < length; i++) {
key = $inject[i];
for (var i = 0, length = $inject.length; i < length; i++) {
var key = $inject[i];
if (typeof key !== 'string') {
throw $injectorMinErr('itkn',
'Incorrect injection token! Expected service name as string, got {0}', key);
}
args.push(
locals && locals.hasOwnProperty(key)
? locals[key]
: getService(key, serviceName)
);
args.push(locals && locals.hasOwnProperty(key) ? locals[key] :
getService(key, serviceName));
}
return args;
}


function invoke(fn, self, locals, serviceName) {
if (typeof locals === 'string') {
serviceName = locals;
locals = null;
}

var args = injectionArgs(fn, locals, serviceName);
if (isArray(fn)) {
fn = fn[length];
fn = fn[fn.length - 1];
}

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


function instantiate(Type, locals, serviceName) {
// Check if Type is annotated and use just the given function at n-1 as parameter
// e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
// Object creation: http://jsperf.com/create-constructor/2
var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null);
var returnedValue = invoke(Type, instance, locals, serviceName);

return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
var ctor = (isArray(Type) ? Type[Type.length - 1] : Type);
var args = injectionArgs(Type, locals, serviceName);
// Empty object at position 0 is ignored for invocation with `new`, but required.
args.unshift({});
/*jshint -W058 */ // Applying a constructor without immediate parentheses is the point here.
return new (Function.prototype.bind.apply(ctor, args));
/*jshint +W058 */
}


return {
invoke: invoke,
instantiate: instantiate,
Expand Down
10 changes: 10 additions & 0 deletions test/auto/injectorSpec.js
Expand Up @@ -270,6 +270,16 @@ describe('injector', function() {
it('should take args before first arrow', function() {
expect(annotate(eval('a => b => b'))).toEqual(['a']);
});

it('should be possible to instantiate ES6 classes', function() {
// Only Chrome (not even the FF we use) supports ES6 classes.
if (!/chrome/i.test(navigator.userAgent)) return;
providers('a', function() { return 'a-value'; });
var clazz = eval('(class { constructor(a) { this.a = a; } aVal() { return this.a; } })');
var instance = injector.instantiate(clazz);
expect(instance).toEqual({a: 'a-value'});
expect(instance.aVal()).toEqual('a-value');
});
/*jshint +W061 */
});
}
Expand Down

0 comments on commit 8b6b428

Please sign in to comment.