-
Notifications
You must be signed in to change notification settings - Fork 27.4k
fix(ngTransclude): 'cos things are never simple #14787
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -163,41 +163,56 @@ 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 = ''; | ||
} | ||
compile: function ngTranscludeCompile(tElement) { | ||
|
||
// Remove and cache any original content to act as a fallback | ||
var fallbackLinkFn = $compile(tElement.contents()); | ||
tElement.empty(); | ||
|
||
return function ngTranscludePostLink($scope, $element, $attrs, controller, $transclude) { | ||
|
||
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)); | ||
} | ||
|
||
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); | ||
|
||
// 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 the attribute is of the form: `ng-transclude="ng-transclude"` then treat it like the default | ||
if ($attrs.ngTransclude === $attrs.$attr.ngTransclude) { | ||
$attrs.ngTransclude = ''; | ||
} | ||
} | ||
var slotName = $attrs.ngTransclude || $attrs.ngTranscludeSlot; | ||
|
||
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 the slot is required and no transclusion content is provided then this call will throw an error | ||
$transclude(ngTranscludeCloneAttachFn, null, slotName); | ||
|
||
// 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 the slot is optional and no transclusion content is provided then use the fallback content | ||
if (slotName && !$transclude.isSlotFilled(slotName)) { | ||
useFallbackContent(); | ||
} | ||
|
||
function ngTranscludeCloneAttachFn(clone, transcludedScope) { | ||
if (clone.length) { | ||
$element.append(clone); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we also release any fallback contents for GC (e.g. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds reasonable There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually this is more complicated. We need to compile this content into a linking function, which we will use to link and clone the fallback content, in the case that this appears inside an ng-repeat. |
||
} else { | ||
useFallbackContent(); | ||
// 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(); | ||
} | ||
} | ||
|
||
function useFallbackContent() { | ||
// Since this is the fallback content rather than the transcluded content, | ||
// we link against the scope of this directive rather than the transcluded scope | ||
fallbackLinkFn($scope, function(clone) { | ||
$element.append(clone); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't this mean, that if the fallback content requires a parent controller, it won't be able to find it (since we append it to the DOM after it has been linked)? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps a suitable unit test? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The append is being done in the cloneAttachFn which occurs prior to link, so it should be able to find any required controllers. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doh! |
||
}); | ||
} | ||
}; | ||
} | ||
}; | ||
}]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How common do you feel it is to use fallback content and have the user not provide their own? Wondering if it's worth calling
$compile
the first time the fallback content is needed rather than at compile time, but I have no idea how common this usage is to determine whether or not that'd be worth it.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is faster than the situation we had before before your PR, since then we were compiling AND linking the fallback content, even if it was never used.
We could optimize to lazy compile the content, but I would like to see some numbers that show it makes any real difference before doing so.