diff --git a/src/ng/compile.js b/src/ng/compile.js index 393ab6375a27..4c2280806e46 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -1486,6 +1486,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { }); }; + // We need to attach the transclusion slots onto the boundTranscludeFn + // so that they are available for directives such as `ngTransclude` + var boundSlots = boundTranscludeFn.$$slots = createMap(); + for (var slotName in transcludeFn.$$slots) { + boundSlots[slotName] = createBoundTranscludeFn(scope, transcludeFn.$$slots[slotName], previousBoundTranscludeFn); + } + return boundTranscludeFn; } @@ -1826,6 +1833,52 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { // on the same element more than once. nonTlbTranscludeDirective: nonTlbTranscludeDirective }); + } else if (isObject(directiveValue)) { + + // We have multiple transclusion zones - match them and collect them up into slots + $template = []; + var slots = createMap(); + 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 will contain true for all slots that are either + // optional or have been filled + filledSlots[slotName] = optional; + }); + + + forEach($compileNode.children(), function(node) { + 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); + } + }); + + // Compile each slot into a transclusion function and attach them to the default transclusion function + childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn); + forEach(Object.keys(slots), function(slotName) { + slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slots[slotName], transcludeFn); + }); + childTranscludeFn.$$slots = slots; + $compileNode.empty(); // clear contents + } else { $template = jqLite(jqLiteClone(compileNode)).contents(); $compileNode.empty(); // clear contents diff --git a/src/ng/directive/ngTransclude.js b/src/ng/directive/ngTransclude.js index 36201a69ab73..9cabb1c29965 100644 --- a/src/ng/directive/ngTransclude.js +++ b/src/ng/directive/ngTransclude.js @@ -8,12 +8,20 @@ * @description * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. * + * You can specify that you want to insert a named transclusion slot, instead of the default slot, by providing the slot name + * as the value of the `ng-transclude` or `ng-transclude-slot` attribute. + * * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted. * * @element ANY * + * @param {string} ngTransclude|ngTranscludeSlot the name of the slot to insert at this point. If this is not provided or empty then + * the default slot is used. + * * @example - + * ### Default transclusion + * This example demonstrates simple transclusion. +