Skip to content

Commit

Permalink
fix($compile): bindToController should work without controllerAs
Browse files Browse the repository at this point in the history
  • Loading branch information
thorn0 committed Sep 8, 2016
1 parent c729554 commit c2532c6
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 230 deletions.
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
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
238 changes: 99 additions & 139 deletions test/ng/compileSpec.js
Expand Up @@ -6126,154 +6126,114 @@ 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!
// Should we allow this to happen?
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

0 comments on commit c2532c6

Please sign in to comment.