-
Notifications
You must be signed in to change notification settings - Fork 27.5k
Commit
Now you can efficiently split up and transclude content into specified places in a component's template. ```html <pane> <pane-title>Some content for slot A</pane-title> <pane-content>Some content for slot A</pane-content> </component> ``` ```js mod.directive('pane', function() { return { restrict: 'E', transclude: { paneTitle: '?titleSlot', paneContent: 'contentSlot' }, template: '<div class="pane">' + '<h1 ng-transclude="titleSlot"></h1>' + '<div ng-transclude="contentSlot"></div>' + '</div>' + }; }); ``` Closes #4357 Closes #12742 Closes #11736 Closes #12934
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1480,6 +1480,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { | |
}); | ||
}; | ||
|
||
// We need to attach the transclusion slots onto the `boundTranscludeFn` | ||
// so that they are available inside the `controllersBoundTransclude` function | ||
var boundSlots = boundTranscludeFn.$$slots = createMap(); | ||
for (var slotName in transcludeFn.$$slots) { | ||
boundSlots[slotName] = createBoundTranscludeFn(scope, transcludeFn.$$slots[slotName], previousBoundTranscludeFn); | ||
} | ||
|
||
return boundTranscludeFn; | ||
} | ||
|
||
|
@@ -1821,9 +1828,56 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { | |
nonTlbTranscludeDirective: nonTlbTranscludeDirective | ||
}); | ||
} else { | ||
|
||
var slots = createMap(); | ||
$template = jqLite(jqLiteClone(compileNode)).contents(); | ||
|
||
if (isObject(directiveValue)) { | ||
|
||
// We have transclusion slots - collect them up and compile them and store their | ||
// transclusion functions | ||
$template = []; | ||
var slotNames = createMap(); | ||
var filledSlots = createMap(); | ||
|
||
// Parse the slot names: if they start with a ? then they are optional | ||
forEach(directiveValue, function(slotName, key) { | ||
var optional = (slotName.charAt(0) === '?'); | ||
slotName = optional ? slotName.substring(1) : slotName; | ||
slotNames[key] = slotName; | ||
slots[slotName] = []; | ||
// filledSlots contains `true` for all slots that are either optional or have been | ||
// filled. This is used to check that we have not missed any required slots | ||
filledSlots[slotName] = optional; | ||
}); | ||
|
||
// Add the matching elements into their slot | ||
forEach($compileNode.children(), function(node) { | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
ggoodman
Contributor
|
||
var slotName = slotNames[directiveNormalize(nodeName_(node))]; | ||
var slot = $template; | ||
if (slotName) { | ||
filledSlots[slotName] = true; | ||
slots[slotName].push(node); | ||
} else { | ||
$template.push(node); | ||
} | ||
}); | ||
|
||
// Check for required slots that were not filled | ||
forEach(filledSlots, function(filled, slotName) { | ||
if (!filled) { | ||
throw $compileMinErr('reqslot', 'Required transclusion slot `{0}` was not filled.', slotName); | ||
} | ||
}); | ||
|
||
forEach(Object.keys(slots), function(slotName) { | ||
slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slots[slotName], transcludeFn); | ||
}); | ||
} | ||
|
||
$compileNode.empty(); // clear contents | ||
childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn); | ||
childTranscludeFn.$$slots = slots; | ||
} | ||
} | ||
|
||
|
@@ -2130,11 +2184,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { | |
|
||
// This is the function that is injected as `$transclude`. | ||
// Note: all arguments are optional! | ||
function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement) { | ||
function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement, slotName) { | ||
var transcludeControllers; | ||
|
||
// No scope passed in: | ||
if (!isScope(scope)) { | ||
slotName = futureParentElement; | ||
futureParentElement = cloneAttachFn; | ||
cloneAttachFn = scope; | ||
scope = undefined; | ||
|
@@ -2146,6 +2200,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { | |
if (!futureParentElement) { | ||
futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element; | ||
} | ||
if (slotName) { | ||
var slotTranscludeFn = boundTranscludeFn.$$slots[slotName]; | ||
if (!slotTranscludeFn) { | ||
throw $compileMinErr('noslot', | ||
'No parent directive that requires a transclusion with slot name "{0}". ' + | ||
'Element: {1}', | ||
slotName, startingTag($element)); | ||
} | ||
return slotTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); | ||
} | ||
return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); | ||
} | ||
} | ||
|
11 comments
on commit a4ada8b
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.
I just want to note that there are two mistakes in the commit message which might get into the changelog:
</component>
should be</pane>
<pane>
<pane-title>Some content for slot A</pane-title>
<pane-content>Some content for slot A</pane-content>
</component> <!-- here -->
- Trailing
+
after</div>
mod.directive('pane', function() {
return {
restrict: 'E',
transclude: { paneTitle: '?titleSlot', paneContent: 'contentSlot' },
template:
'<div class="pane">' +
'<h1 ng-transclude="titleSlot"></h1>' +
'<div ng-transclude="contentSlot"></div>' +
'</div>' + // here
};
});
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.
Thanks! @campersau - I will make sure we catch those on the changelog.
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 has been a long time coming. Awesome work!
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.
I cant get this to work with current 1.5.0 beta1... https://code.angularjs.org/1.5.0-beta.1/angular.js
(maybe I should make a seperate issue? - or maybe its just not in yet?)
Made a simple codepen to show off: http://codepen.io/filipbech/pen/pjVvNM
It just transcludes both slot-contents into both slots...
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.
The feature is not in 1.5.0-beta.1.
See http://codepen.io/anon/pen/NGMGYY, which uses the master build
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.
Perfect. Thanks!
Any guess how far away 1.5 is?
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.
Days or weeks at most
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.
@petebacondarwin Amazing work! It covers every use case I could come up with!
Also, I really love the fact that it's very easy to add new slots on top of existing directives without creating "default" slots.
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.
Finally! This solves one of our biggest pains in UI Bootstrap, not being able to pass through multiple content ala Web Components!
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.
Hi,
using "Multi-slot" transclusion and the new "component()" function (Ng version 1.5.0-rc.0)
there seems to be no way for the inner component to use the API of the outer component:
<outer>
<inner on-close="outerclose()"></inner>
</outer>
... where "outer" component (NG 1.5 component) exposes an API "outerclose()" to it's nested directive.
The only way I could coax it into work was something like that:
<outer vm="vm">
<inner on-close="vm.outer.outerclose"></inner>
</outer>
function OuterCtrl () {
var outer = this;
// exposing the API
outer.outerclose = function () { blah };
// Trick, extending the surrounding scope (vm) of <outer> with outer's API
outer.vm.outer = outer;
}
But is not the right approach I guess - however how is supposed to access API then?
Thank you!
guygit
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.
@guygit - could you create an issue to discuss this and reference me in it so that I notice it.
The use of
.children()
appears to have the side-effect that any non-HTMLElement
nodes are skipped and therefore dropped from the default transclusion slot. Attn: @petebacondarwin.