From 8df5f3259aa776f28bf3d869fb1c03e10a897c84 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Fri, 23 May 2014 12:01:44 +0100 Subject: [PATCH] fix($compile): pass transcludeFn down to nested transclude directives If you have two directives that both expect to receive transcluded content the outer directive works but the inner directive never receives a transclusion function. This only failed if the first transclude directive was not the first directive found in compilation. Handles the regression identified in e994259739821094e77a3d2c1f30c28713b7ab3a Fixes #7240 Closes #7387 --- src/ng/compile.js | 23 ++++++--- test/ng/compileSpec.js | 109 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 8 deletions(-) diff --git a/src/ng/compile.js b/src/ng/compile.js index e783307615cf..f73b4dc743d2 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -919,7 +919,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { return linkFnFound ? compositeLinkFn : null; function compositeLinkFn(scope, nodeList, $rootElement, boundTranscludeFn) { - var nodeLinkFn, childLinkFn, node, $node, childScope, childTranscludeFn, i, ii, n; + var nodeLinkFn, childLinkFn, node, $node, childScope, i, ii, n, childBoundTranscludeFn; // copy nodeList so that linking doesn't break due to live list updates. var nodeListLength = nodeList.length, @@ -941,14 +941,19 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } else { childScope = scope; } - childTranscludeFn = nodeLinkFn.transclude; - if (childTranscludeFn || (!boundTranscludeFn && transcludeFn)) { - nodeLinkFn(childLinkFn, childScope, node, $rootElement, - createBoundTranscludeFn(scope, childTranscludeFn || transcludeFn) - ); + + // We need to create a new boundTranscludeFn if + // - a directive on this element wants to transclude + // or + // - there is no boundTranscludeFn already and a transcludeFn was passed in + if ( nodeLinkFn.transcludeOnThisElement || (!boundTranscludeFn && transcludeFn) ) { + childBoundTranscludeFn = createBoundTranscludeFn(scope, nodeLinkFn.transclude || transcludeFn); } else { - nodeLinkFn(childLinkFn, childScope, node, $rootElement, boundTranscludeFn); + childBoundTranscludeFn = boundTranscludeFn; } + + nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn); + } else if (childLinkFn) { childLinkFn(scope, node.childNodes, undefined, boundTranscludeFn); } @@ -1324,7 +1329,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; - nodeLinkFn.transclude = hasTranscludeDirective && childTranscludeFn; + nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective; + nodeLinkFn.transclude = childTranscludeFn; + previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; // might be normal or delayed nodeLinkFn depending on if templateUrl is present diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index 496ff8dada3d..55a410eefdeb 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -3982,6 +3982,115 @@ describe('$compile', function() { }); }); + + + describe('nested transcludes', function() { + + beforeEach(module(function($compileProvider) { + + $compileProvider.directive('noop', valueFn({})); + + $compileProvider.directive('sync', valueFn({ + template: '
', + transclude: true + })); + + $compileProvider.directive('async', valueFn({ + templateUrl: 'async', + transclude: true + })); + + $compileProvider.directive('syncSync', valueFn({ + template: '
', + transclude: true + })); + + $compileProvider.directive('syncAsync', valueFn({ + template: '
', + transclude: true + })); + + $compileProvider.directive('asyncSync', valueFn({ + templateUrl: 'asyncSync', + transclude: true + })); + + $compileProvider.directive('asyncAsync', valueFn({ + templateUrl: 'asyncAsync', + transclude: true + })); + + })); + + beforeEach(inject(function($templateCache) { + $templateCache.put('async', '
'); + $templateCache.put('asyncSync', '
'); + $templateCache.put('asyncAsync', '
'); + })); + + + it('should allow nested transclude directives with sync template containing sync template', inject(function($compile, $rootScope) { + element = $compile('
transcluded content
')($rootScope); + $rootScope.$digest(); + expect(element.text()).toEqual('transcluded content'); + })); + + it('should allow nested transclude directives with sync template containing async template', inject(function($compile, $rootScope) { + element = $compile('
transcluded content
')($rootScope); + $rootScope.$digest(); + expect(element.text()).toEqual('transcluded content'); + })); + + it('should allow nested transclude directives with async template containing sync template', inject(function($compile, $rootScope) { + element = $compile('
transcluded content
')($rootScope); + $rootScope.$digest(); + expect(element.text()).toEqual('transcluded content'); + })); + + it('should allow nested transclude directives with async template containing asynch template', inject(function($compile, $rootScope) { + element = $compile('
transcluded content
')($rootScope); + $rootScope.$digest(); + expect(element.text()).toEqual('transcluded content'); + })); + }); + + + describe('multiple siblings receiving transclusion', function() { + + it("should only receive transclude from parent", function() { + + module(function($compileProvider) { + + $compileProvider.directive('myExample', valueFn({ + scope: {}, + link: function link(scope, element, attrs) { + var foo = element[0].querySelector('.foo'); + scope.children = angular.element(foo).children().length; + }, + template: '
' + + '
myExample {{children}}!
' + + '
has children
' + + '
' + + '
', + transclude: true + + })); + + }); + + inject(function($compile, $rootScope) { + var element = $compile('
')($rootScope); + $rootScope.$digest(); + expect(element.text()).toEqual('myExample 0!'); + dealoc(element); + + element = $compile('

')($rootScope); + $rootScope.$digest(); + expect(element.text()).toEqual('myExample 1!has children'); + dealoc(element); + }); + }); + }); });