Skip to content

Commit

Permalink
fix($compile): bind all directive controllers correctly when using `b…
Browse files Browse the repository at this point in the history
…indToController`

Previously only the first directive's controller would be bound correctly.

Closes angular#11343
Closes angular#11345
  • Loading branch information
jtorbicki authored and gkalpak committed Nov 23, 2015
1 parent dc677b0 commit 594104b
Show file tree
Hide file tree
Showing 2 changed files with 198 additions and 32 deletions.
53 changes: 21 additions & 32 deletions src/ng/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -2084,7 +2084,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}

function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
var i, ii, linkFn, controller, isolateScope, elementControllers, transcludeFn, $element,
var linkFn, isolateScope, elementControllers, transcludeFn, $element,
attrs, removeScopeBindingWatches, removeControllerBindingWatches;

if (compileNode === linkNode) {
Expand Down Expand Up @@ -2124,38 +2124,27 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
isolateScope.$on('$destroy', removeScopeBindingWatches);
}
}
if (elementControllers) {
// Initialize bindToController bindings for new/isolate scopes
var scopeDirective = newIsolateScopeDirective || newScopeDirective;
var bindings;
var controllerForBindings;
if (scopeDirective && elementControllers[scopeDirective.name]) {
bindings = scopeDirective.$$bindings.bindToController;
controller = elementControllers[scopeDirective.name];

if (controller && controller.identifier && bindings) {
controllerForBindings = controller;
removeControllerBindingWatches =
initializeDirectiveBindings(scope, attrs, controller.instance,
bindings, scopeDirective);
}

// Initialize bindToController bindings
for (var name in elementControllers) {
var controllerDirective = controllerDirectives[name];
var controller = elementControllers[name];
var bindings = controllerDirective.$$bindings.bindToController;

if (controller.identifier && bindings) {
removeControllerBindingWatches =
initializeDirectiveBindings(scope, attrs, controller.instance, bindings, controllerDirective);
}
for (i in elementControllers) {
controller = elementControllers[i];
var controllerResult = controller();

if (controllerResult !== controller.instance) {
// If the controller constructor has a return value, overwrite the instance
// from setupControllers and update the element data
controller.instance = controllerResult;
$element.data('$' + i + 'Controller', controllerResult);
if (controller === controllerForBindings) {
// Remove and re-install bindToController bindings
removeControllerBindingWatches && removeControllerBindingWatches();
removeControllerBindingWatches =
initializeDirectiveBindings(scope, attrs, controllerResult, bindings, scopeDirective);
}
}

var controllerResult = controller();
if (controllerResult !== controller.instance) {
// If the controller constructor has a return value, overwrite the instance
// from setupControllers
controller.instance = controllerResult;
$element.data('$' + controllerDirective.name + 'Controller', controllerResult);
removeControllerBindingWatches && removeControllerBindingWatches();
removeControllerBindingWatches =
initializeDirectiveBindings(scope, attrs, controller.instance, bindings, controllerDirective);
}
}

Expand Down
177 changes: 177 additions & 0 deletions test/ng/compileSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4414,6 +4414,183 @@ describe('$compile', function() {
});


it('should bind to multiple directives controllers via object notation (no scope)', function() {
var controller1Called = false;
var controller2Called = false;
module(function($compileProvider, $controllerProvider) {
$compileProvider.directive('foo', valueFn({
bindToController: {
'data': '=fooData',
'str': '@fooStr',
'fn': '&fooFn'
},
controllerAs: 'fooCtrl',
controller: function() {
expect(this.data).toEqualData({'foo': 'bar', 'baz': 'biz'});
expect(this.str).toBe('Hello, world!');
expect(this.fn()).toBe('called!');
controller1Called = true;
}
}));
$compileProvider.directive('bar', valueFn({
bindToController: {
'data': '=barData',
'str': '@barStr',
'fn': '&barFn'
},
controllerAs: 'barCtrl',
controller: function() {
expect(this.data).toEqualData({'foo2': 'bar2', 'baz2': 'biz2'});
expect(this.str).toBe('Hello, second world!');
expect(this.fn()).toBe('second called!');
controller2Called = true;
}
}));
});
inject(function($compile, $rootScope) {
$rootScope.fn = valueFn('called!');
$rootScope.string = 'world';
$rootScope.data = {'foo': 'bar','baz': 'biz'};
$rootScope.fn2 = valueFn('second called!');
$rootScope.string2 = 'second world';
$rootScope.data2 = {'foo2': 'bar2', 'baz2': 'biz2'};
element = $compile(
'<div ' +
'foo ' +
'foo-data="data" ' +
'foo-str="Hello, {{string}}!" ' +
'foo-fn="fn()" ' +
'bar ' +
'bar-data="data2" ' +
'bar-str="Hello, {{string2}}!" ' +
'bar-fn="fn2()" > ' +
'</div>')($rootScope);
$rootScope.$digest();
expect(controller1Called).toBe(true);
expect(controller2Called).toBe(true);
});
});


it('should bind to multiple directives controllers via object notation (new iso scope)', function() {
var controller1Called = false;
var controller2Called = false;
module(function($compileProvider, $controllerProvider) {
$compileProvider.directive('foo', valueFn({
bindToController: {
'data': '=fooData',
'str': '@fooStr',
'fn': '&fooFn'
},
scope: true,
controllerAs: 'fooCtrl',
controller: function() {
expect(this.data).toEqualData({'foo': 'bar', 'baz': 'biz'});
expect(this.str).toBe('Hello, world!');
expect(this.fn()).toBe('called!');
controller1Called = true;
}
}));
$compileProvider.directive('bar', valueFn({
bindToController: {
'data': '=barData',
'str': '@barStr',
'fn': '&barFn'
},
controllerAs: 'barCtrl',
controller: function() {
expect(this.data).toEqualData({'foo2': 'bar2', 'baz2': 'biz2'});
expect(this.str).toBe('Hello, second world!');
expect(this.fn()).toBe('second called!');
controller2Called = true;
}
}));
});
inject(function($compile, $rootScope) {
$rootScope.fn = valueFn('called!');
$rootScope.string = 'world';
$rootScope.data = {'foo': 'bar','baz': 'biz'};
$rootScope.fn2 = valueFn('second called!');
$rootScope.string2 = 'second world';
$rootScope.data2 = {'foo2': 'bar2', 'baz2': 'biz2'};
element = $compile(
'<div ' +
'foo ' +
'foo-data="data" ' +
'foo-str="Hello, {{string}}!" ' +
'foo-fn="fn()" ' +
'bar ' +
'bar-data="data2" ' +
'bar-str="Hello, {{string2}}!" ' +
'bar-fn="fn2()" > ' +
'</div>')($rootScope);
$rootScope.$digest();
expect(controller1Called).toBe(true);
expect(controller2Called).toBe(true);
});
});


it('should bind to multiple directives controllers via object notation (new scope)', function() {
var controller1Called = false;
var controller2Called = false;
module(function($compileProvider, $controllerProvider) {
$compileProvider.directive('foo', valueFn({
bindToController: {
'data': '=fooData',
'str': '@fooStr',
'fn': '&fooFn'
},
scope: true,
controllerAs: 'fooCtrl',
controller: function() {
expect(this.data).toEqualData({'foo': 'bar', 'baz': 'biz'});
expect(this.str).toBe('Hello, world!');
expect(this.fn()).toBe('called!');
controller1Called = true;
}
}));
$compileProvider.directive('bar', valueFn({
bindToController: {
'data': '=barData',
'str': '@barStr',
'fn': '&barFn'
},
scope: true,
controllerAs: 'barCtrl',
controller: function() {
expect(this.data).toEqualData({'foo2': 'bar2', 'baz2': 'biz2'});
expect(this.str).toBe('Hello, second world!');
expect(this.fn()).toBe('second called!');
controller2Called = true;
}
}));
});
inject(function($compile, $rootScope) {
$rootScope.fn = valueFn('called!');
$rootScope.string = 'world';
$rootScope.data = {'foo': 'bar','baz': 'biz'};
$rootScope.fn2 = valueFn('second called!');
$rootScope.string2 = 'second world';
$rootScope.data2 = {'foo2': 'bar2', 'baz2': 'biz2'};
element = $compile(
'<div ' +
'foo ' +
'foo-data="data" ' +
'foo-str="Hello, {{string}}!" ' +
'foo-fn="fn()" ' +
'bar ' +
'bar-data="data2" ' +
'bar-str="Hello, {{string2}}!" ' +
'bar-fn="fn2()" > ' +
'</div>')($rootScope);
$rootScope.$digest();
expect(controller1Called).toBe(true);
expect(controller2Called).toBe(true);
});
});


it('should put controller in scope when controller identifier present but not using controllerAs', function() {
var controllerCalled = false;
var myCtrl;
Expand Down

0 comments on commit 594104b

Please sign in to comment.