fix($compile): bindToController should work without controllerAs
Fixes #15088 Closes #15110
This commit is contained in:
committed by
Georgios Kalpakas
parent
a1bdffa12f
commit
16dccea887
@@ -1,71 +0,0 @@
|
||||
@ngdoc error
|
||||
@name $compile:noident
|
||||
@fullName Controller identifier is required.
|
||||
@description
|
||||
|
||||
When using the `bindToController` feature of AngularJS, a directive is required
|
||||
to have a Controller identifier, which is initialized in scope with the value of
|
||||
the controller instance. This can be supplied using the "controllerAs" property
|
||||
of the directive object, or alternatively by adding " as IDENTIFIER" to the controller
|
||||
name.
|
||||
|
||||
For example, the following directives are valid:
|
||||
|
||||
```js
|
||||
// OKAY, because controller is a string with an identifier component.
|
||||
directive("okay", function() {
|
||||
return {
|
||||
bindToController: true,
|
||||
controller: "myCtrl as $ctrl",
|
||||
scope: {
|
||||
text: "@text"
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
// OKAY, because the directive uses the controllerAs property to override
|
||||
// the controller identifier.
|
||||
directive("okay2", function() {
|
||||
return {
|
||||
bindToController: true,
|
||||
controllerAs: "$ctrl",
|
||||
controller: function() {
|
||||
|
||||
},
|
||||
scope: {
|
||||
text: "@text"
|
||||
}
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
While the following are invalid:
|
||||
|
||||
```js
|
||||
// BAD, because the controller property is a string with no identifier.
|
||||
directive("bad", function() {
|
||||
return {
|
||||
bindToController: true,
|
||||
controller: "noIdentCtrl",
|
||||
scope: {
|
||||
text: "@text"
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
// BAD because the controller is not a string (therefore has no identifier),
|
||||
// and there is no controllerAs property.
|
||||
directive("bad2", function() {
|
||||
return {
|
||||
bindToController: true,
|
||||
controller: function noControllerAs() {
|
||||
|
||||
},
|
||||
scope: {
|
||||
text: "@text"
|
||||
}
|
||||
};
|
||||
});
|
||||
```
|
||||
+8
-20
@@ -361,9 +361,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.
|
||||
@@ -1028,20 +1026,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;
|
||||
}
|
||||
@@ -2710,7 +2699,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 {
|
||||
@@ -3413,8 +3402,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 = {};
|
||||
|
||||
+99
-140
@@ -6205,154 +6205,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'
|
||||
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'
|
||||
}
|
||||
};
|
||||
|
||||
forEach([controllerOption, scopeOption, templateOption], function(option) {
|
||||
description.push(option.description);
|
||||
delete option.description;
|
||||
extend(ddo, option);
|
||||
});
|
||||
expect(this.oneway).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).toBeDefined();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
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'
|
||||
});
|
||||
expect(this.data).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: 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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user