From 06ecb4b6ecfc25e658656d800a30458537f72222 Mon Sep 17 00:00:00 2001 From: Martin Probst Date: Mon, 17 Aug 2015 13:28:45 +0200 Subject: [PATCH] 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. --- src/auto/injector.js | 48 ++++++++++++++++++++++----------------- test/auto/injectorSpec.js | 10 ++++++++ 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/auto/injector.js b/src/auto/injector.js index 925287fcce7f..e00f3bd066a9 100644 --- a/src/auto/injector.js +++ b/src/auto/injector.js @@ -801,31 +801,33 @@ 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 @@ -833,16 +835,20 @@ function createInjector(modulesToLoad, strictDi) { 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, diff --git a/test/auto/injectorSpec.js b/test/auto/injectorSpec.js index 0ebc509403e1..981b006491d8 100644 --- a/test/auto/injectorSpec.js +++ b/test/auto/injectorSpec.js @@ -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 */ }); }