From 594104b3e407440d3158adc22026987aba67acfb Mon Sep 17 00:00:00 2001 From: Jakub Torbicki Date: Wed, 18 Mar 2015 10:16:14 +0100 Subject: [PATCH] fix($compile): bind all directive controllers correctly when using `bindToController` Previously only the first directive's controller would be bound correctly. Closes #11343 Closes #11345 --- src/ng/compile.js | 53 +++++------- test/ng/compileSpec.js | 177 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 198 insertions(+), 32 deletions(-) diff --git a/src/ng/compile.js b/src/ng/compile.js index 270d563b727e..2376257b5331 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -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) { @@ -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); } } diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index c265ffab952e..136bec90a25b 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -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( + '
' + + '
')($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( + '
' + + '
')($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( + '
' + + '
')($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;