Skip to content

Commit

Permalink
feat($compile): Allow ES6 classes as controllers with `bindToControll…
Browse files Browse the repository at this point in the history
…er: true`

Modify `$injector.invoke` so ES6 classes would be invoked using `new`

Closes: angular#13510
Closes: angular#13540
  • Loading branch information
lgalfaso committed Jan 5, 2016
1 parent e020b89 commit ef3c9a2
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 5 deletions.
23 changes: 19 additions & 4 deletions src/auto/injector.js
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,14 @@ function createInjector(modulesToLoad, strictDi) {
return args;
}

function isClass(func) {
// IE 9-11 do not support classes and IE9 leaks with the code below.
if (msie <= 11) {
return false;
}
return typeof func === 'function'
&& /^class\s/.test(Function.prototype.toString.call(func));
}

function invoke(fn, self, locals, serviceName) {
if (typeof locals === 'string') {
Expand All @@ -833,9 +841,16 @@ function createInjector(modulesToLoad, strictDi) {
fn = fn[fn.length - 1];
}

// http://jsperf.com/angularjs-invoke-apply-vs-switch
// #5388
return fn.apply(self, args);
if (!isClass(fn)) {
// http://jsperf.com/angularjs-invoke-apply-vs-switch
// #5388
return fn.apply(self, args);
} else {
args.unshift(null);
/*jshint -W058 */ // Applying a constructor without immediate parentheses is the point here.
return new (Function.prototype.bind.apply(fn, args));
/*jshint +W058 */
}
}


Expand All @@ -845,7 +860,7 @@ function createInjector(modulesToLoad, strictDi) {
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({});
args.unshift(null);
/*jshint -W058 */ // Applying a constructor without immediate parentheses is the point here.
return new (Function.prototype.bind.apply(ctor, args));
/*jshint +W058 */
Expand Down
2 changes: 1 addition & 1 deletion src/ng/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@
* #### `bindToController`
* When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController: true` will
* allow a component to have its properties bound to the controller, rather than to scope. When the controller
* is instantiated, the initial values of the isolate scope bindings are already available.
* is instantiated, the initial values of the isolate scope bindings will be available if the controller is not an ES6 class.
*
* #### `controller`
* Controller constructor function. The controller is instantiated before the
Expand Down
47 changes: 47 additions & 0 deletions test/ng/compileSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4235,6 +4235,53 @@ describe('$compile', function() {
});


it('should eventually expose isolate scope variables on ES6 class controller with controllerAs when bindToController is true', function() {
if (!/chrome/i.test(navigator.userAgent)) return;
/*jshint -W061 */
var controllerCalled = false;
module(function($compileProvider) {
$compileProvider.directive('fooDir', valueFn({
template: '<p>isolate</p>',
scope: {
'data': '=dirData',
'str': '@dirStr',
'fn': '&dirFn'
},
controller: eval(
"class Foo {" +
" constructor($scope) {}" +
" check() {" +
" expect(this.data).toEqualData({" +
" 'foo': 'bar'," +
" 'baz': 'biz'" +
" });" +
" expect(this.str).toBe('Hello, world!');" +
" expect(this.fn()).toBe('called!');" +
" controllerCalled = true;" +
" }" +
"}"
),
controllerAs: 'test',
bindToController: true
}));
});
inject(function($compile, $rootScope) {
$rootScope.fn = valueFn('called!');
$rootScope.whom = 'world';
$rootScope.remoteData = {
'foo': 'bar',
'baz': 'biz'
};
element = $compile('<div foo-dir dir-data="remoteData" ' +
'dir-str="Hello, {{whom}}!" ' +
'dir-fn="fn()"></div>')($rootScope);
element.data('$fooDirController').check();
expect(controllerCalled).toBe(true);
});
/*jshint +W061 */
});


it('should update @-bindings on controller when bindToController and attribute change observed', function() {
module(function($compileProvider) {
$compileProvider.directive('atBinding', valueFn({
Expand Down

0 comments on commit ef3c9a2

Please sign in to comment.