From 09bfbd5aba96f59a0cd4113659e031fc05608fff Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Fri, 23 May 2014 12:01:44 +0100 Subject: [PATCH 1/9] 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); + }); + }); + }); }); From 33fc41bdec56977f767e6cb18bc45d0f410cfbd5 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Fri, 23 May 2014 12:09:15 +0100 Subject: [PATCH 2/9] refactor($compile): change parameter name The boundTransclusionFn that is passed in is really the one from the parent node. The change to parentBoundTranscludeFn clarifies this compared to the childBoundTranscludeFn. --- src/ng/compile.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ng/compile.js b/src/ng/compile.js index f73b4dc743d2..c1cc4bcb48ed 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -918,7 +918,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { // return a linking function if we have found anything, null otherwise return linkFnFound ? compositeLinkFn : null; - function compositeLinkFn(scope, nodeList, $rootElement, boundTranscludeFn) { + function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) { var nodeLinkFn, childLinkFn, node, $node, childScope, i, ii, n, childBoundTranscludeFn; // copy nodeList so that linking doesn't break due to live list updates. @@ -945,17 +945,17 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { // 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) ) { + // - there is no parentBoundTranscludeFn already and a transcludeFn was passed in + if ( nodeLinkFn.transcludeOnThisElement || (!parentBoundTranscludeFn && transcludeFn) ) { childBoundTranscludeFn = createBoundTranscludeFn(scope, nodeLinkFn.transclude || transcludeFn); } else { - childBoundTranscludeFn = boundTranscludeFn; + childBoundTranscludeFn = parentBoundTranscludeFn; } nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn); } else if (childLinkFn) { - childLinkFn(scope, node.childNodes, undefined, boundTranscludeFn); + childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn); } } } From a7def05d178d275088b41e7f7fd90f3365130b33 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Fri, 23 May 2014 12:11:28 +0100 Subject: [PATCH 3/9] fix($compile): fix nested isolated transclude directives Closes #1809 Closes #7499 --- src/ng/compile.js | 18 +++++++++--- test/ng/compileSpec.js | 67 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/src/ng/compile.js b/src/ng/compile.js index c1cc4bcb48ed..1c0bd85341f9 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -947,7 +947,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { // or // - there is no parentBoundTranscludeFn already and a transcludeFn was passed in if ( nodeLinkFn.transcludeOnThisElement || (!parentBoundTranscludeFn && transcludeFn) ) { - childBoundTranscludeFn = createBoundTranscludeFn(scope, nodeLinkFn.transclude || transcludeFn); + childBoundTranscludeFn = createBoundTranscludeFn(scope, nodeLinkFn.transclude || transcludeFn, parentBoundTranscludeFn); } else { childBoundTranscludeFn = parentBoundTranscludeFn; } @@ -961,8 +961,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } } - function createBoundTranscludeFn(scope, transcludeFn) { - return function boundTranscludeFn(transcludedScope, cloneFn, controllers) { + function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) { + + // If there is a previous boundTransclude function and it has a transclusionScope then + // use this instead of the current scope + scope = previousBoundTranscludeFn && previousBoundTranscludeFn.transclusionScope || scope; + + var boundTranscludeFn = function(transcludedScope, cloneFn, controllers) { var scopeCreated = false; if (!transcludedScope) { @@ -977,6 +982,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } return clone; }; + + // Store the transclusionScope for nested transclusions + boundTranscludeFn.transclusionScope = scope; + + return boundTranscludeFn; } /** @@ -1749,7 +1759,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { safeAddClass(jqLite(linkNode), oldClasses); } if (afterTemplateNodeLinkFn.transclude) { - childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude); + childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); } else { childBoundTranscludeFn = boundTranscludeFn; } diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index 55a410eefdeb..577437369128 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -4055,6 +4055,73 @@ describe('$compile', function() { }); + describe('nested isolated scope transcludes', function() { + beforeEach(module(function($compileProvider) { + + $compileProvider.directive('trans', valueFn({ + restrict: 'E', + template: '
', + transclude: true + })); + + $compileProvider.directive('transAsync', valueFn({ + restrict: 'E', + templateUrl: 'transAsync', + transclude: true + })); + + $compileProvider.directive('iso', valueFn({ + restrict: 'E', + transclude: true, + template: '', + scope: {} + })); + $compileProvider.directive('isoAsync1', valueFn({ + restrict: 'E', + transclude: true, + template: '', + scope: {} + })); + $compileProvider.directive('isoAsync2', valueFn({ + restrict: 'E', + transclude: true, + templateUrl: 'isoAsync', + scope: {} + })); + })); + + beforeEach(inject(function($templateCache) { + $templateCache.put('transAsync', '
'); + $templateCache.put('isoAsync', ''); + })); + + + it('should pass the outer scope to the transclude on the isolated template sync-sync', inject(function($compile, $rootScope) { + + $rootScope.val = 'transcluded content'; + element = $compile('')($rootScope); + $rootScope.$digest(); + expect(element.text()).toEqual('transcluded content'); + })); + + it('should pass the outer scope to the transclude on the isolated template async-sync', inject(function($compile, $rootScope) { + + $rootScope.val = 'transcluded content'; + element = $compile('')($rootScope); + $rootScope.$digest(); + expect(element.text()).toEqual('transcluded content'); + })); + + it('should pass the outer scope to the transclude on the isolated template async-async', inject(function($compile, $rootScope) { + + $rootScope.val = 'transcluded content'; + element = $compile('')($rootScope); + $rootScope.$digest(); + expect(element.text()).toEqual('transcluded content'); + })); + + }); + describe('multiple siblings receiving transclusion', function() { it("should only receive transclude from parent", function() { From 4cc0cf7fabf80246e61a12856b9bb132d8a191a8 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Fri, 23 May 2014 11:44:45 +0100 Subject: [PATCH 4/9] refactor($compile): no need to use bind --- src/ng/compile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ng/compile.js b/src/ng/compile.js index 1c0bd85341f9..b499dcf23b77 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -978,7 +978,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { var clone = transcludeFn(transcludedScope, cloneFn, controllers); if (scopeCreated) { - clone.on('$destroy', bind(transcludedScope, transcludedScope.$destroy)); + clone.on('$destroy', function() { transcludedScope.$destroy(); }); } return clone; }; From b8455179aa54fd7901dcdac5c1d65490cce6e7ee Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Fri, 23 May 2014 12:13:27 +0100 Subject: [PATCH 5/9] fix($compile): don't pass transclude to template of non-transclude directive If a directive provides a template but is not explicitly requesting transclusion then the compiler should not pass a transclusion function to the directives within the template. --- src/ng/compile.js | 25 +++++++++++++++++-------- test/ng/compileSpec.js | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/ng/compile.js b/src/ng/compile.js index b499dcf23b77..a1ec901a7549 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -907,7 +907,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { !childNodes.length) ? null : compileNodes(childNodes, - nodeLinkFn ? nodeLinkFn.transclude : transcludeFn); + nodeLinkFn ? ( + (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement) + && nodeLinkFn.transclude) : transcludeFn); linkFns.push(nodeLinkFn, childLinkFn); linkFnFound = linkFnFound || nodeLinkFn || childLinkFn; @@ -942,14 +944,17 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { childScope = scope; } - // We need to create a new boundTranscludeFn if - // - a directive on this element wants to transclude - // or - // - there is no parentBoundTranscludeFn already and a transcludeFn was passed in - if ( nodeLinkFn.transcludeOnThisElement || (!parentBoundTranscludeFn && transcludeFn) ) { - childBoundTranscludeFn = createBoundTranscludeFn(scope, nodeLinkFn.transclude || transcludeFn, parentBoundTranscludeFn); - } else { + if ( nodeLinkFn.transcludeOnThisElement ) { + childBoundTranscludeFn = createBoundTranscludeFn(scope, nodeLinkFn.transclude, parentBoundTranscludeFn); + + } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) { childBoundTranscludeFn = parentBoundTranscludeFn; + + } else if (!parentBoundTranscludeFn && transcludeFn) { + childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn); + + } else { + childBoundTranscludeFn = null; } nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn); @@ -1164,6 +1169,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { templateDirective = previousCompileContext.templateDirective, nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, hasTranscludeDirective = false, + hasTemplate = false, hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective, $compileNode = templateAttrs.$$element = jqLite(compileNode), directive, @@ -1254,6 +1260,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } if (directive.template) { + hasTemplate = true; assertNoDuplicate('template', templateDirective, directive, $compileNode); templateDirective = directive; @@ -1303,6 +1310,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } if (directive.templateUrl) { + hasTemplate = true; assertNoDuplicate('template', templateDirective, directive, $compileNode); templateDirective = directive; @@ -1340,6 +1348,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective; + nodeLinkFn.templateOnThisElement = hasTemplate; nodeLinkFn.transclude = childTranscludeFn; previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index 577437369128..a42629f62575 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -3764,6 +3764,39 @@ describe('$compile', function() { }); + it('should not pass transclusion into a template directive when the directive didn\'t request transclusion', function() { + + module(function($compileProvider) { + + $compileProvider.directive('transFoo', valueFn({ + template: '
' + + '
' + + '
this one should get replaced with content
' + + '
' + + '
', + transclude: true + + })); + + $compileProvider.directive('noTransBar', valueFn({ + template: '
' + + // This ng-transclude is invalid. It should throw an error. + '
' + + '
', + transclude: false + + })); + }); + + inject(function($compile, $rootScope) { + expect(function() { + $compile('
content
')($rootScope); + }).toThrowMinErr('ngTransclude', 'orphan', + 'Illegal use of ngTransclude directive in the template! No parent directive that requires a transclusion found. Element:
'); + }); + }); + + it('should make the result of a transclusion available to the parent directive in post-linking phase' + '(template)', function() { module(function() { From b33285729b3d758d7e0099386a20c292567fe1ff Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Mon, 26 May 2014 22:21:20 +0100 Subject: [PATCH 6/9] fix($compile): don't pass transcludes to non-transclude templateUrl directives --- src/ng/compile.js | 2 +- test/ng/compileSpec.js | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/ng/compile.js b/src/ng/compile.js index a1ec901a7549..639b220ba959 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -1319,7 +1319,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, - templateAttrs, jqCollection, childTranscludeFn, preLinkFns, postLinkFns, { + templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, { controllerDirectives: controllerDirectives, newIsolateScopeDirective: newIsolateScopeDirective, templateDirective: templateDirective, diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index a42629f62575..eed66baf320d 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -3797,6 +3797,42 @@ describe('$compile', function() { }); + it('should not pass transclusion into a templateUrl directive', function() { + + module(function($compileProvider) { + + $compileProvider.directive('transFoo', valueFn({ + template: '
' + + '
' + + '
this one should get replaced with content
' + + '
' + + '
', + transclude: true + + })); + + $compileProvider.directive('noTransBar', valueFn({ + templateUrl: 'noTransBar.html', + transclude: false + + })); + }); + + inject(function($compile, $rootScope, $templateCache) { + $templateCache.put('noTransBar.html', + '
' + + // This ng-transclude is invalid. It should throw an error. + '
' + + '
'); + + expect(function() { + element = $compile('
content
')($rootScope); + $rootScope.$apply(); + }).toThrowMinErr('ngTransclude', 'orphan', + 'Illegal use of ngTransclude directive in the template! No parent directive that requires a transclusion found. Element:
'); + }); + }); + it('should make the result of a transclusion available to the parent directive in post-linking phase' + '(template)', function() { module(function() { From 4addd89082088a68cfbfc93d381a6aa64df4f8b6 Mon Sep 17 00:00:00 2001 From: Vojta Jina Date: Tue, 27 May 2014 15:11:45 -0700 Subject: [PATCH 7/9] fix($compile): bound transclusion to correct scope MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nested isolated transclude directives. This improves/fixes the fix in d414b787173643362c0c513a1929d8e715ca340e. See the changed ng-ifunit test: The template inside ng-if should be bound to the isolate scope of `iso` directive (resp. its child scope). Not to a child of the root scope. This shows the issue with ng-if. It’s however problem with other directives too. Instead of remembering the scope, we pass around the bound parent transclusion. Conflicts: test/ng/directive/ngIfSpec.js --- src/ng/compile.js | 15 ++++----------- test/ng/directive/ngIfSpec.js | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/ng/compile.js b/src/ng/compile.js index 639b220ba959..d82a11f5c044 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -830,7 +830,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority, ignoreDirective, previousCompileContext); safeAddClass($compileNodes, 'ng-scope'); - return function publicLinkFn(scope, cloneConnectFn, transcludeControllers){ + return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn){ assertArg(scope, 'scope'); // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart // and sometimes changes the structure of the DOM. @@ -852,7 +852,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } if (cloneConnectFn) cloneConnectFn($linkNode, scope); - if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode); + if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn); return $linkNode; }; } @@ -968,10 +968,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) { - // If there is a previous boundTransclude function and it has a transclusionScope then - // use this instead of the current scope - scope = previousBoundTranscludeFn && previousBoundTranscludeFn.transclusionScope || scope; - var boundTranscludeFn = function(transcludedScope, cloneFn, controllers) { var scopeCreated = false; @@ -981,16 +977,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { scopeCreated = true; } - var clone = transcludeFn(transcludedScope, cloneFn, controllers); + var clone = transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn); if (scopeCreated) { clone.on('$destroy', function() { transcludedScope.$destroy(); }); } return clone; }; - // Store the transclusionScope for nested transclusions - boundTranscludeFn.transclusionScope = scope; - return boundTranscludeFn; } @@ -1767,7 +1760,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { // Copy in CSS classes from original node safeAddClass(jqLite(linkNode), oldClasses); } - if (afterTemplateNodeLinkFn.transclude) { + if (afterTemplateNodeLinkFn.transcludeOnThisElement) { childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); } else { childBoundTranscludeFn = boundTranscludeFn; diff --git a/test/ng/directive/ngIfSpec.js b/test/ng/directive/ngIfSpec.js index 771e264a9a73..c4c253cca893 100755 --- a/test/ng/directive/ngIfSpec.js +++ b/test/ng/directive/ngIfSpec.js @@ -199,6 +199,28 @@ describe('ngIf and transcludes', function() { dealoc(element); }); }); + + + it('should use the correct transcluded scope', function() { + module(function($compileProvider) { + $compileProvider.directive('iso', valueFn({ + link: function(scope) { + scope.val = 'value in iso scope'; + }, + restrict: 'E', + transclude: true, + template: '
val={{val}}-
', + scope: {} + })); + }); + inject(function($compile, $rootScope) { + $rootScope.val = 'transcluded content'; + var element = $compile('')($rootScope); + $rootScope.$digest(); + expect(trim(element.text())).toEqual('val=value in iso scope-transcluded content'); + dealoc(element); + }); + }); }); describe('ngIf animations', function () { From 280ed9f0d625b4aee7921071e452ac61d4776292 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Fri, 13 Jun 2014 09:24:25 +0100 Subject: [PATCH 8/9] fix($compile): ensure transclude works at root of templateUrl If a "replace" directive has an async template, which contains a transclusion directive at its root node, then outer transclusions were failing to be passed to this directive. An example would be uses of `ngIf` inside and outside the template. Collaborated with @caitp Closes #7183 Closes #7772 --- src/ng/compile.js | 9 +++++--- test/ng/compileSpec.js | 51 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/ng/compile.js b/src/ng/compile.js index d82a11f5c044..f7ca7af38585 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -1738,7 +1738,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { }); afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); - while(linkQueue.length) { var scope = linkQueue.shift(), beforeTemplateLinkNode = linkQueue.shift(), @@ -1775,13 +1774,17 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { }); return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { + var childBoundTranscludeFn = boundTranscludeFn; if (linkQueue) { linkQueue.push(scope); linkQueue.push(node); linkQueue.push(rootElement); - linkQueue.push(boundTranscludeFn); + linkQueue.push(childBoundTranscludeFn); } else { - afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, boundTranscludeFn); + if (afterTemplateNodeLinkFn.transcludeOnThisElement) { + childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); + } + afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn); } }; } diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index eed66baf320d..a76f6d1d9d94 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -4563,6 +4563,57 @@ describe('$compile', function() { expect(element.text()).toBe('-->|x|'); })); + + + // See https://github.com/angular/angular.js/issues/7183 + it("should pass transclusion through to template of a 'replace' directive", function() { + module(function() { + directive('transSync', function() { + return { + transclude: true, + link: function(scope, element, attr, ctrl, transclude) { + + expect(transclude).toEqual(jasmine.any(Function)); + + transclude(function(child) { element.append(child); }); + } + }; + }); + + directive('trans', function($timeout) { + return { + transclude: true, + link: function(scope, element, attrs, ctrl, transclude) { + + // We use timeout here to simulate how ng-if works + $timeout(function() { + transclude(function(child) { element.append(child); }); + }); + } + }; + }); + + directive('replaceWithTemplate', function() { + return { + templateUrl: "template.html", + replace: true + }; + }); + }); + + inject(function($compile, $rootScope, $templateCache, $timeout) { + + $templateCache.put('template.html', '
Content To Be Transcluded
'); + + expect(function() { + element = $compile('
')($rootScope); + $timeout.flush(); + }).not.toThrow(); + + expect(element.text()).toEqual('Content To Be Transcluded'); + }); + + }); }); From cb16bc940257e9621e972148bebc63ffa7d52e6d Mon Sep 17 00:00:00 2001 From: Caitlin Potter Date: Fri, 13 Jun 2014 10:20:53 -0400 Subject: [PATCH 9/9] test($compile): make IE8 happy, maybe --- test/ng/compileSpec.js | 43 +++++++++++++++++++++++------------ test/ng/directive/ngIfSpec.js | 4 ++-- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index a76f6d1d9d94..480bde531a1d 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -3789,10 +3789,18 @@ describe('$compile', function() { }); inject(function($compile, $rootScope) { + var message = 'Illegal use of ngTransclude directive in the template! No parent ' + + 'directive that requires a transclusion found. Element:
'; + if (msie <= 8) { + // MSIE ヽ(`Д´)ノ + message = 'Illegal use of ngTransclude directive in the template! No parent ' + + 'directive that requires a transclusion found. Element:
'; + } expect(function() { $compile('
content
')($rootScope); - }).toThrowMinErr('ngTransclude', 'orphan', - 'Illegal use of ngTransclude directive in the template! No parent directive that requires a transclusion found. Element:
'); + }).toThrowMinErr('ngTransclude', 'orphan', message); }); }); @@ -3824,12 +3832,17 @@ describe('$compile', function() { // This ng-transclude is invalid. It should throw an error. '
' + '
'); - + var message = 'Illegal use of ngTransclude directive in the template! No parent directive that ' + + 'requires a transclusion found. Element:
'; + if (msie <= 8) { + // MSIE ヽ(`Д´)ノ + message = 'Illegal use of ngTransclude directive in the template! No parent directive that ' + + 'requires a transclusion found. Element:
'; + } expect(function() { element = $compile('
content
')($rootScope); $rootScope.$apply(); - }).toThrowMinErr('ngTransclude', 'orphan', - 'Illegal use of ngTransclude directive in the template! No parent directive that requires a transclusion found. Element:
'); + }).toThrowMinErr('ngTransclude', 'orphan', message); }); }); @@ -4134,25 +4147,25 @@ describe('$compile', function() { })); $compileProvider.directive('transAsync', valueFn({ - restrict: 'E', + restrict: 'A', templateUrl: 'transAsync', transclude: true })); $compileProvider.directive('iso', valueFn({ - restrict: 'E', + restrict: 'A', transclude: true, - template: '', + template: '
', scope: {} })); $compileProvider.directive('isoAsync1', valueFn({ - restrict: 'E', + restrict: 'A', transclude: true, - template: '', + template: '
', scope: {} })); $compileProvider.directive('isoAsync2', valueFn({ - restrict: 'E', + restrict: 'A', transclude: true, templateUrl: 'isoAsync', scope: {} @@ -4161,14 +4174,14 @@ describe('$compile', function() { beforeEach(inject(function($templateCache) { $templateCache.put('transAsync', '
'); - $templateCache.put('isoAsync', ''); + $templateCache.put('isoAsync', '
'); })); it('should pass the outer scope to the transclude on the isolated template sync-sync', inject(function($compile, $rootScope) { $rootScope.val = 'transcluded content'; - element = $compile('')($rootScope); + element = $compile('
')($rootScope); $rootScope.$digest(); expect(element.text()).toEqual('transcluded content'); })); @@ -4176,7 +4189,7 @@ describe('$compile', function() { it('should pass the outer scope to the transclude on the isolated template async-sync', inject(function($compile, $rootScope) { $rootScope.val = 'transcluded content'; - element = $compile('')($rootScope); + element = $compile('
')($rootScope); $rootScope.$digest(); expect(element.text()).toEqual('transcluded content'); })); @@ -4184,7 +4197,7 @@ describe('$compile', function() { it('should pass the outer scope to the transclude on the isolated template async-async', inject(function($compile, $rootScope) { $rootScope.val = 'transcluded content'; - element = $compile('')($rootScope); + element = $compile('
')($rootScope); $rootScope.$digest(); expect(element.text()).toEqual('transcluded content'); })); diff --git a/test/ng/directive/ngIfSpec.js b/test/ng/directive/ngIfSpec.js index c4c253cca893..3a8beaef4b3b 100755 --- a/test/ng/directive/ngIfSpec.js +++ b/test/ng/directive/ngIfSpec.js @@ -207,7 +207,7 @@ describe('ngIf and transcludes', function() { link: function(scope) { scope.val = 'value in iso scope'; }, - restrict: 'E', + restrict: 'A', transclude: true, template: '
val={{val}}-
', scope: {} @@ -215,7 +215,7 @@ describe('ngIf and transcludes', function() { }); inject(function($compile, $rootScope) { $rootScope.val = 'transcluded content'; - var element = $compile('')($rootScope); + var element = $compile('
')($rootScope); $rootScope.$digest(); expect(trim(element.text())).toEqual('val=value in iso scope-transcluded content'); dealoc(element);