Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit df45ab8

Browse files
author
Kara Erickson
committed
feat($compile): add multi option to transclude property
Previously, there were two options to the transclude property: true and "element". This commit adds a third option, "multi", that automatically matches transcluded elements to elements in the directive template with matching ng-transclude-select selectors.
1 parent c10b249 commit df45ab8

File tree

3 files changed

+90
-3
lines changed

3 files changed

+90
-3
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
@ngdoc error
2+
@name $compile:invalidmulti
3+
@fullName Invalid multi transclusion.
4+
@description
5+
6+
When using a directive that allows multiple transclusion points (e.g. the `transclude` property is set to `multi` in the directive definition), each transcluded element needs to match an `ng-transclude-select` selector in the directive template. Otherwise, it won't be properly appended to the DOM.
7+
8+
This error occurs when the element does not match `ng-transclude-select` selectors in the directive template.
9+

src/ng/compile.js

+54-2
Original file line numberDiff line numberDiff line change
@@ -316,13 +316,16 @@
316316
* The contents are compiled and provided to the directive as a **transclusion function**. See the
317317
* {@link $compile#transclusion Transclusion} section below.
318318
*
319-
* There are two kinds of transclusion depending upon whether you want to transclude just the contents of the
320-
* directive's element or the entire element:
319+
* There are three kinds of transclusion depending upon whether you want to transclude just the contents of the
320+
* directive's element, the entire element, or transclude to multiple points in the directive.
321321
*
322322
* * `true` - transclude the content (i.e. the child nodes) of the directive's element.
323323
* * `'element'` - transclude the whole of the directive's element including any directives on this
324324
* element that defined at a lower priority than this directive. When used, the `template`
325325
* property is ignored.
326+
* * `'multi'` - allows transclusion into multiple points of the directive's template. When used, automatically
327+
* appends any transcluded elements that match `ng-transclude-select` selector to the `ng-transclude-select` element
328+
* in the directive template.
326329
*
327330
*
328331
* #### `compile`
@@ -1645,6 +1648,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
16451648
templateDirective = previousCompileContext.templateDirective,
16461649
nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective,
16471650
hasTranscludeDirective = false,
1651+
hasMultiTranscludeDirective = false,
16481652
hasTemplate = false,
16491653
hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective,
16501654
$compileNode = templateAttrs.$$element = jqLite(compileNode),
@@ -1737,6 +1741,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
17371741
nonTlbTranscludeDirective: nonTlbTranscludeDirective
17381742
});
17391743
} else {
1744+
if (directiveValue == 'multi') {
1745+
hasMultiTranscludeDirective = true;
1746+
}
17401747
$template = jqLite(jqLiteClone(compileNode)).contents();
17411748
$compileNode.empty(); // clear contents
17421749
childTranscludeFn = compile($template, transcludeFn);
@@ -1833,6 +1840,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
18331840
nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true;
18341841
nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective;
18351842
nodeLinkFn.elementTranscludeOnThisElement = hasElementTranscludeDirective;
1843+
nodeLinkFn.multiTranscludeOnThisElement = hasMultiTranscludeDirective;
18361844
nodeLinkFn.templateOnThisElement = hasTemplate;
18371845
nodeLinkFn.transclude = childTranscludeFn;
18381846

@@ -2027,6 +2035,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
20272035
}
20282036
childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);
20292037

2038+
if (thisLinkFn.multiTranscludeOnThisElement) multiTransclude($element[0], transcludeFn);
2039+
20302040
// POSTLINKING
20312041
for (i = postLinkFns.length - 1; i >= 0; i--) {
20322042
linkFn = postLinkFns[i];
@@ -2128,6 +2138,48 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
21282138
return false;
21292139
}
21302140

2141+
/**
2142+
* Matches elements in the transcluded content to elements in the directive template by ng-transclude-selector.
2143+
* Used in directives that set transclude to 'multi'.
2144+
*
2145+
* @param {jqLite} dirElement Main directive element
2146+
* @param {function()} transcludeFn Transclusion function for the directive
2147+
**/
2148+
function multiTransclude(dirElement, transcludeFn) {
2149+
transcludeFn(transcludeCallback);
2150+
2151+
function transcludeCallback(clone) {
2152+
var target,
2153+
selector,
2154+
selectedElements,
2155+
transcludeTargets = dirElement.querySelectorAll('[ng-transclude-select]'),
2156+
cloneWrapper = jqLite("<span></span>");
2157+
cloneWrapper.append(clone);
2158+
2159+
for (var i = 0, ii = transcludeTargets.length; i < ii; i++) {
2160+
target = jqLite(transcludeTargets[i]);
2161+
selector = target.attr('ng-transclude-select');
2162+
selectedElements = cloneWrapper[0].querySelectorAll(selector);
2163+
if (selectedElements.length) target.append(selectedElements);
2164+
}
2165+
checkForTranscludeErr(cloneWrapper);
2166+
cloneWrapper.remove();
2167+
}
2168+
2169+
function checkForTranscludeErr(cloneWrapper) {
2170+
var orphanElement;
2171+
if (cloneWrapper.children().length) {
2172+
orphanElement = jqLite(cloneWrapper.children()[0]);
2173+
cloneWrapper.children().remove();
2174+
throw $compileMinErr('invalidmulti',
2175+
'Invalid transclusion. Element {0} does not match any known ng-transclude-select targets.',
2176+
startingTag(orphanElement));
2177+
}
2178+
2179+
}
2180+
}
2181+
2182+
21312183
/**
21322184
* When the element is replaced with HTML template then the new attributes
21332185
* on the template need to be merged with the existing attributes in the DOM.

test/ng/compileSpec.js

+27-1
Original file line numberDiff line numberDiff line change
@@ -6311,7 +6311,7 @@ describe('$compile', function() {
63116311
});
63126312

63136313

6314-
it('should terminate compilation only for element trasclusion', function() {
6314+
it('should terminate compilation only for element transclusion', function() {
63156315
module(function() {
63166316
directive('elementTrans', function(log) {
63176317
return {
@@ -6585,6 +6585,32 @@ describe('$compile', function() {
65856585
});
65866586
});
65876587

6588+
describe('multi transclusion', function() {
6589+
beforeEach(module(function() {
6590+
directive('transclude', valueFn({
6591+
transclude: 'multi',
6592+
scope: {},
6593+
template: '<div ng-transclude-select="[top]"></div><div ng-transclude-select="[bottom]"></div>'
6594+
}));
6595+
}));
6596+
it('should append ng-transclude-to elements to matching ng-tranclude-ids', inject(function($compile, $rootScope) {
6597+
var topTarget, bottomTarget;
6598+
element = $compile(
6599+
'<div transclude><div bottom>In bottom.</div><div top>In top.</div></div></div>'
6600+
)($rootScope);
6601+
topTarget = jqLite(element[0].querySelector('[ng-transclude-select="[top]"]'));
6602+
bottomTarget = jqLite(element[0].querySelector('[ng-transclude-select="[bottom]"]'));
6603+
expect(topTarget.text()).toEqual('In top.');
6604+
expect(bottomTarget.text()).toEqual('In bottom.');
6605+
}));
6606+
it('should throw error if transcluded element does not match any ng-transclude-selects', inject(function($compile, $rootScope) {
6607+
expect(function() {
6608+
$compile('<div transclude><div>In bottom.</div><div>In top.</div></div>')($rootScope);
6609+
}).toThrowMinErr(
6610+
'$compile', 'invalidmulti', 'Invalid transclusion. Element <div class="ng-scope"> ' +
6611+
'does not match any known ng-transclude-select targets.');
6612+
}));
6613+
});
65886614

65896615
describe('img[src] sanitization', function() {
65906616

0 commit comments

Comments
 (0)