diff --git a/src/ng/compile.js b/src/ng/compile.js index afafb081cf72..b333b079aa82 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -1293,6 +1293,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { return function publicLinkFn(scope, cloneConnectFn, options) { assertArg(scope, 'scope'); + if (previousCompileContext && previousCompileContext.needsNewScope) { + // A parent directive did a replace and a directive on this element asked + // for transclusion, which caused us to lose a layer of element on which + // we could hold the new transclusion scope, so we will create it manually + // here. + scope = scope.$parent.$new(); + } + options = options || {}; var parentBoundTranscludeFn = options.parentBoundTranscludeFn, transcludeControllers = options.transcludeControllers, @@ -1874,7 +1882,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } $compileNode.empty(); // clear contents - childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn); + childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, undefined, + undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope}); childTranscludeFn.$$slots = slots; } } @@ -1917,8 +1926,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); - if (newIsolateScopeDirective) { - markDirectivesAsIsolate(templateDirectives); + if (newIsolateScopeDirective || newScopeDirective) { + // The original directive caused the current element to be replaced but this element + // also needs to have a new scope, so we need to tell the template directives + // that they would need to get their scope from further up, if they require transclusion + markDirectiveScope(templateDirectives, newIsolateScopeDirective, newScopeDirective); } directives = directives.concat(templateDirectives).concat(unprocessedDirectives); mergeTemplateAttributes(templateAttrs, newTemplateAttrs); @@ -2072,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, controllerScope, elementControllers, transcludeFn, $element, attrs, removeScopeBindingWatches, removeControllerBindingWatches; if (compileNode === linkNode) { @@ -2083,8 +2095,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { attrs = new Attributes($element, templateAttrs); } + controllerScope = scope; if (newIsolateScopeDirective) { isolateScope = scope.$new(true); + } else if (newScopeDirective) { + controllerScope = scope.$parent; } if (boundTranscludeFn) { @@ -2112,38 +2127,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(controllerScope, 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(controllerScope, attrs, controller.instance, bindings, controllerDirective); } } @@ -2213,10 +2217,15 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } } - function markDirectivesAsIsolate(directives) { - // mark all directives as needing isolate scope. + // Depending upon the context in which a directive finds itself it might need to have a new isolated + // or child scope created. For instance: + // * if the directive has been pulled into a template because another directive with a higher priority + // asked for element transclusion + // * if the directive itself asks for transclusion but it is at the root of a template and the original + // element was replaced. See https://github.com/angular/angular.js/issues/12936 + function markDirectiveScope(directives, isolateScope, newScope) { for (var j = 0, jj = directives.length; j < jj; j++) { - directives[j] = inherit(directives[j], {$$isolateScope: true}); + directives[j] = inherit(directives[j], {$$isolateScope: isolateScope, $$newScope: newScope}); } } @@ -2363,7 +2372,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); if (isObject(origAsyncDirective.scope)) { - markDirectivesAsIsolate(templateDirectives); + // the original directive that caused the template to be loaded async required + // an isolate scope + markDirectiveScope(templateDirectives, true); } directives = templateDirectives.concat(directives); mergeTemplateAttributes(tAttrs, tempTemplateAttrs); diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index da016f74c5ac..ee09676bc4dd 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -4414,6 +4414,301 @@ 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( + '