From facad293002e672ab576b26af11696f89b413a39 Mon Sep 17 00:00:00 2001 From: Daniel Herman Date: Tue, 14 Jun 2016 18:04:41 -0400 Subject: [PATCH] fix(ngTransclude): correctly support fallback content If the transclude function did not return content, then the transcluded scope that was created needs to be cleaned up in order to avoid a slight amount of unnecessary overhead since the additional scope is no longer needed. If the transcluded content did return content, the the fallback content should never have been linked in the first place as it was intended to be immediately removed. Fixes #14768 Fixes #14765 --- src/ng/directive/ngTransclude.js | 64 ++++++++++++++++++-------------- test/ng/compileSpec.js | 32 ++++++++++++++-- 2 files changed, 66 insertions(+), 30 deletions(-) diff --git a/src/ng/directive/ngTransclude.js b/src/ng/directive/ngTransclude.js index c55537b2fdd0..5b31b06a2248 100644 --- a/src/ng/directive/ngTransclude.js +++ b/src/ng/directive/ngTransclude.js @@ -159,35 +159,45 @@ * */ var ngTranscludeMinErr = minErr('ngTransclude'); -var ngTranscludeDirective = ngDirective({ - restrict: 'EAC', - link: function($scope, $element, $attrs, controller, $transclude) { +var ngTranscludeDirective = ['$compile', function($compile) { + return { + restrict: 'EAC', + terminal: true, + link: function($scope, $element, $attrs, controller, $transclude) { + if ($attrs.ngTransclude === $attrs.$attr.ngTransclude) { + // If the attribute is of the form: `ng-transclude="ng-transclude"` + // then treat it like the default + $attrs.ngTransclude = ''; + } - if ($attrs.ngTransclude === $attrs.$attr.ngTransclude) { - // If the attribute is of the form: `ng-transclude="ng-transclude"` - // then treat it like the default - $attrs.ngTransclude = ''; - } + function ngTranscludeCloneAttachFn(clone, transcludedScope) { + if (clone.length) { + $element.empty(); + $element.append(clone); + } else { + // Since this is the fallback content rather than the transcluded content, + // we compile against the scope we were linked against rather than the transcluded + // scope since this is the directive's own content + $compile($element.contents())($scope); - function ngTranscludeCloneAttachFn(clone) { - if (clone.length) { - $element.empty(); - $element.append(clone); + // There is nothing linked against the transcluded scope since no content was available, + // so it should be safe to clean up the generated scope. + transcludedScope.$destroy(); + } } - } - if (!$transclude) { - throw ngTranscludeMinErr('orphan', - 'Illegal use of ngTransclude directive in the template! ' + - 'No parent directive that requires a transclusion found. ' + - 'Element: {0}', - startingTag($element)); - } - - // If there is no slot name defined or the slot name is not optional - // then transclude the slot - var slotName = $attrs.ngTransclude || $attrs.ngTranscludeSlot; - $transclude(ngTranscludeCloneAttachFn, null, slotName); - } -}); + if (!$transclude) { + throw ngTranscludeMinErr('orphan', + 'Illegal use of ngTransclude directive in the template! ' + + 'No parent directive that requires a transclusion found. ' + + 'Element: {0}', + startingTag($element)); + } + // If there is no slot name defined or the slot name is not optional + // then transclude the slot + var slotName = $attrs.ngTransclude || $attrs.ngTranscludeSlot; + $transclude(ngTranscludeCloneAttachFn, null, slotName); + } + }; +}]; diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index dde7eeb4df8d..99feac76b00a 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -7937,11 +7937,23 @@ describe('$compile', function() { it('should clear contents of the ng-translude element before appending transcluded content' + ' if transcluded content exists', function() { + var contentsDidLink = false; + module(function() { + directive('inner', function() { + return { + restrict: 'E', + template: 'old stuff! ', + link: function() { + contentsDidLink = true; + } + }; + }); + directive('trans', function() { return { transclude: true, - template: '
old stuff!
' + template: '
' }; }); }); @@ -7949,23 +7961,37 @@ describe('$compile', function() { element = $compile('
unicorn!
')($rootScope); $rootScope.$apply(); expect(sortedHtml(element.html())).toEqual('
unicorn!
'); + expect(contentsDidLink).toBe(false); }); }); it('should NOT clear contents of the ng-translude element before appending transcluded content' + ' if transcluded content does NOT exist', function() { + var contentsDidLink = false; + module(function() { + directive('inner', function() { + return { + restrict: 'E', + template: 'old stuff! ', + link: function() { + contentsDidLink = true; + } + }; + }); + directive('trans', function() { return { transclude: true, - template: '
old stuff!
' + template: '
' }; }); }); inject(function(log, $rootScope, $compile) { element = $compile('
')($rootScope); $rootScope.$apply(); - expect(sortedHtml(element.html())).toEqual('
old stuff!
'); + expect(sortedHtml(element.html())).toEqual('
old stuff!
'); + expect(contentsDidLink).toBe(true); }); });