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

fix($compile): bindToController should work without controllerAs #15110

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 0 additions & 71 deletions docs/content/error/$compile/noident.ngdoc

This file was deleted.

28 changes: 8 additions & 20 deletions src/ng/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -360,9 +360,7 @@
*
* #### `bindToController`
* This property is used to bind scope properties directly to the controller. It can be either
* `true` or an object hash with the same format as the `scope` property. Additionally, a controller
* alias must be set, either by using `controllerAs: 'myAlias'` or by specifying the alias in the controller
* definition: `controller: 'myCtrl as myAlias'`.
* `true` or an object hash with the same format as the `scope` property.
*
* When an isolate scope is used for a directive (see above), `bindToController: true` will
* allow a component to have its properties bound to the controller, rather than to scope.
Expand Down Expand Up @@ -1027,20 +1025,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
bindings.bindToController =
parseIsolateBindings(directive.bindToController, directiveName, true);
}
if (isObject(bindings.bindToController)) {
var controller = directive.controller;
var controllerAs = directive.controllerAs;
if (!controller) {
// There is no controller, there may or may not be a controllerAs property
throw $compileMinErr('noctrl',
'Cannot bind to controller without directive \'{0}\'s controller.',
directiveName);
} else if (!identifierForController(controller, controllerAs)) {
// There is a controller, but no identifier or controllerAs property
throw $compileMinErr('noident',
'Cannot bind to controller without identifier for directive \'{0}\'.',
directiveName);
}
if (bindings.bindToController && !directive.controller) {
// There is no controller
throw $compileMinErr('noctrl',
'Cannot bind to controller without directive \'{0}\'s controller.',
directiveName);
}
return bindings;
}
Expand Down Expand Up @@ -2709,7 +2698,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
var bindings = controllerDirective.$$bindings.bindToController;

if (preAssignBindingsEnabled) {
if (controller.identifier && bindings) {
if (bindings) {
controller.bindingInfo =
initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
} else {
Expand Down Expand Up @@ -3412,8 +3401,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}


// Set up $watches for isolate scope and controller bindings. This process
// only occurs for isolate scopes and new scopes with controllerAs.
// Set up $watches for isolate scope and controller bindings.
function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) {
var removeWatchCollection = [];
var initialChanges = {};
Expand Down
237 changes: 98 additions & 139 deletions test/ng/compileSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6126,154 +6126,113 @@ describe('$compile', function() {
});


it('should throw noident when missing controllerAs directive property', function() {
module(function($compileProvider) {
$compileProvider.directive('noIdent', valueFn({
templateUrl: 'test.html',
scope: {
'data': '=dirData',
'oneway': '<dirData',
'str': '@dirStr',
'fn': '&dirFn'
},
controller: function() {},
bindToController: true
}));
});
inject(function($compile, $rootScope) {
expect(function() {
$compile('<div no-ident>')($rootScope);
}).toThrowMinErr('$compile', 'noident',
'Cannot bind to controller without identifier for directive \'noIdent\'.');
});
});


it('should throw noident when missing controller identifier', function() {
module(function($compileProvider, $controllerProvider) {
$controllerProvider.register('myCtrl', function() {});
$compileProvider.directive('noIdent', valueFn({
templateUrl: 'test.html',
scope: {
'data': '=dirData',
'oneway': '<dirData',
'str': '@dirStr',
'fn': '&dirFn'
},
describe('should bind to controller via object notation', function() {
var controllerOptions = [{
description: 'no controller identifier',
controller: 'myCtrl'
}, {
description: '"Ctrl as ident" syntax',
controller: 'myCtrl as myCtrl'
}, {
description: 'controllerAs setting',
controller: 'myCtrl',
bindToController: true
}));
});
inject(function($compile, $rootScope) {
expect(function() {
$compile('<div no-ident>')($rootScope);
}).toThrowMinErr('$compile', 'noident',
'Cannot bind to controller without identifier for directive \'noIdent\'.');
});
});
controllerAs: 'myCtrl'
}],

scopeOptions = [{
description: 'isolate scope',
scope: {}
}, {
description: 'new scope',
scope: true
}, {
description: 'no scope',
scope: false
}],

it('should bind to controller via object notation (isolate scope)', function() {
var controllerCalled = false;
module(function($compileProvider, $controllerProvider) {
$controllerProvider.register('myCtrl', function() {
this.check = function() {
expect(this.data).toEqualData({
'foo': 'bar',
'baz': 'biz'
});
expect(this.oneway).toEqualData({
'foo': 'bar',
'baz': 'biz'
});
expect(this.str).toBe('Hello, world!');
expect(this.fn()).toBe('called!');
};
controllerCalled = true;
if (preAssignBindingsEnabled) {
this.check();
} else {
this.$onInit = this.check;
}
});
$compileProvider.directive('fooDir', valueFn({
templateUrl: 'test.html',
bindToController: {
'data': '=dirData',
'oneway': '<dirData',
'str': '@dirStr',
'fn': '&dirFn'
},
scope: {},
controller: 'myCtrl as myCtrl'
}));
});
inject(function($compile, $rootScope, $templateCache) {
$templateCache.put('test.html', '<p>isolate</p>');
$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);
$rootScope.$digest();
expect(controllerCalled).toBe(true);
});
});
templateOptions = [{
description: 'inline template',
template: '<p>template</p>'
}, {
description: 'templateUrl setting',
templateUrl: 'test.html'
}, {
description: 'no template'
}];

forEach(controllerOptions, function(controllerOption) {
forEach(scopeOptions, function(scopeOption) {
forEach(templateOptions, function(templateOption) {

var description = [],
ddo = {
bindToController: {
'data': '=dirData',
'oneway': '<dirData',
'str': '@dirStr',
'fn': '&dirFn'
}
};

it('should bind to controller via object notation (new scope)', function() {
var controllerCalled = false;
module(function($compileProvider, $controllerProvider) {
$controllerProvider.register('myCtrl', function() {
this.check = function() {
expect(this.data).toEqualData({
'foo': 'bar',
'baz': 'biz'
forEach([controllerOption, scopeOption, templateOption], function(option) {
description.push(option.description);
delete option.description;
extend(ddo, option);
});
expect(this.data).toEqualData({
'foo': 'bar',
'baz': 'biz'

it('(' + description.join(', ') + ')', function() {
var controllerCalled = false;
module(function($compileProvider, $controllerProvider) {
$controllerProvider.register('myCtrl', function() {
this.check = function() {
expect(this.data).toEqualData({
'foo': 'bar',
'baz': 'biz'
});
expect(this.oneway).toEqualData({
'foo': 'bar',
'baz': 'biz'
});
expect(this.str).toBe('Hello, world!');
expect(this.fn()).toBe('called!');
};
controllerCalled = true;
if (preAssignBindingsEnabled) {
this.check();
} else {
this.$onInit = this.check;
}
});
$compileProvider.directive('fooDir', valueFn(ddo));
});
inject(function($compile, $rootScope, $templateCache) {
$templateCache.put('test.html', '<p>template</p>');
$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);
$rootScope.$digest();
expect(controllerCalled).toBe(true);
if (ddo.controllerAs || ddo.controller.indexOf(' as ') !== -1) {
if (ddo.scope) {
expect($rootScope.myCtrl).toBeUndefined();
} else {
// The controller identifier was added to the containing scope.
expect($rootScope.myCtrl).not.toBeUndefined();
}
}
});
});
expect(this.str).toBe('Hello, world!');
expect(this.fn()).toBe('called!');
};
controllerCalled = true;
if (preAssignBindingsEnabled) {
this.check();
} else {
this.$onInit = this.check;
}

});
});
$compileProvider.directive('fooDir', valueFn({
templateUrl: 'test.html',
bindToController: {
'data': '=dirData',
'oneway': '<dirData',
'str': '@dirStr',
'fn': '&dirFn'
},
scope: true,
controller: 'myCtrl as myCtrl'
}));
});
inject(function($compile, $rootScope, $templateCache) {
$templateCache.put('test.html', '<p>isolate</p>');
$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);
$rootScope.$digest();
expect(controllerCalled).toBe(true);
});

});


Expand Down