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

Commit 7af6e3a

Browse files
feat($compile): multiple transclusion via slots
1 parent 3af71be commit 7af6e3a

File tree

3 files changed

+141
-6
lines changed

3 files changed

+141
-6
lines changed

src/ng/compile.js

+28-2
Original file line numberDiff line numberDiff line change
@@ -1486,6 +1486,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
14861486
});
14871487
};
14881488

1489+
var boundSlots = boundTranscludeFn.$$slots = createMap();
1490+
for (var slotName in transcludeFn.$$slots) {
1491+
boundSlots[slotName] = createBoundTranscludeFn(scope, transcludeFn.$$slots[slotName], previousBoundTranscludeFn);
1492+
}
1493+
14891494
return boundTranscludeFn;
14901495
}
14911496

@@ -1646,6 +1651,27 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
16461651
};
16471652
}
16481653

1654+
1655+
function compileTemplate(eager, $templateNodes, parentTranscludeFn, terminalPriority, ignoreDirective, previousCompileContext) {
1656+
var defaultSlot = [];
1657+
var slots = createMap();
1658+
forEach($templateNodes, function(node) {
1659+
var slotName = jqLite(node).attr('ng-transclude-slot');
1660+
var slot = defaultSlot;
1661+
if (slotName) {
1662+
slot = slots[slotName] = (slots[slotName] || []);
1663+
}
1664+
slot.push(node);
1665+
});
1666+
1667+
var transcludeFn = compilationGenerator(eager, defaultSlot, parentTranscludeFn, terminalPriority, ignoreDirective, previousCompileContext);
1668+
forEach(Object.keys(slots), function(slotName) {
1669+
slots[slotName] = compilationGenerator(eager, slots[slotName], parentTranscludeFn, terminalPriority, ignoreDirective, previousCompileContext);
1670+
});
1671+
transcludeFn.$$slots = slots;
1672+
return transcludeFn;
1673+
}
1674+
16491675
/**
16501676
* A function generator that is used to support both eager and lazy compilation
16511677
* linking function.
@@ -1815,7 +1841,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
18151841
compileNode = $compileNode[0];
18161842
replaceWith(jqCollection, sliceArgs($template), compileNode);
18171843

1818-
childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, terminalPriority,
1844+
childTranscludeFn = compileTemplate(mightHaveMultipleTransclusionError, $template, transcludeFn, terminalPriority,
18191845
replaceDirective && replaceDirective.name, {
18201846
// Don't pass in:
18211847
// - controllerDirectives - otherwise we'll create duplicates controllers
@@ -1829,7 +1855,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
18291855
} else {
18301856
$template = jqLite(jqLiteClone(compileNode)).contents();
18311857
$compileNode.empty(); // clear contents
1832-
childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn);
1858+
childTranscludeFn = compileTemplate(mightHaveMultipleTransclusionError, $template, transcludeFn);
18331859
}
18341860
}
18351861

src/ng/directive/ngTransclude.js

+15-4
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,20 @@ var ngTranscludeDirective = ngDirective({
6565
startingTag($element));
6666
}
6767

68-
$transclude(function(clone) {
69-
$element.empty();
70-
$element.append(clone);
71-
});
68+
if ($attrs.ngTransclude) {
69+
$transclude = $transclude.$$boundTransclude.$$slots[$attrs.ngTransclude];
70+
if (!$transclude) return;
71+
72+
$transclude(undefined, function(clone) {
73+
$element.empty();
74+
$element.append(clone);
75+
});
76+
} else {
77+
78+
$transclude(function(clone) {
79+
$element.empty();
80+
$element.append(clone);
81+
});
82+
}
7283
}
7384
});

test/ng/compileSpec.js

+98
Original file line numberDiff line numberDiff line change
@@ -7262,6 +7262,104 @@ describe('$compile', function() {
72627262
});
72637263

72647264

7265+
describe('multi-slot transclude', function() {
7266+
it('should only include elements without `ng-transclude-slot` attribute in default transclusion function', function() {
7267+
module(function() {
7268+
directive('trans', function() {
7269+
return {
7270+
transclude: true,
7271+
template: '<div ng-transclude></div>'
7272+
};
7273+
});
7274+
});
7275+
inject(function($rootScope, $compile) {
7276+
element = $compile(
7277+
'<div trans>' +
7278+
'<span>stuart</span>' +
7279+
'<span>bob</span>' +
7280+
'<span ng-transclude-slot="boss">gru</span>' +
7281+
'<span>kevin</span>' +
7282+
'</div>')($rootScope);
7283+
$rootScope.$apply();
7284+
expect(element.text()).toEqual('stuartbobkevin');
7285+
});
7286+
});
7287+
7288+
it('should transclude elements to an `ng-transclude` with a matching `ng-transclude-slot`', function() {
7289+
module(function() {
7290+
directive('trans', function() {
7291+
return {
7292+
transclude: true,
7293+
template:
7294+
'<div class="boss" ng-transclude="boss"></div>' +
7295+
'<div class="minion" ng-transclude="minion"></div>' +
7296+
'<div class="other" ng-transclude></div>'
7297+
};
7298+
});
7299+
});
7300+
inject(function($rootScope, $compile) {
7301+
element = $compile(
7302+
'<div trans>' +
7303+
'<span ng-transclude-slot="minion">stuart</span>' +
7304+
'<span>dorothy</span>' +
7305+
'<span ng-transclude-slot="boss">gru</span>' +
7306+
'<span ng-transclude-slot="minion">kevin</span>' +
7307+
'</div>')($rootScope);
7308+
$rootScope.$apply();
7309+
expect(element.children().eq(0).text()).toEqual('gru');
7310+
expect(element.children().eq(1).text()).toEqual('stuartkevin');
7311+
expect(element.children().eq(2).text()).toEqual('dorothy');
7312+
});
7313+
});
7314+
7315+
it('should provide the elements marked with `ng-transclude-slot` as additional transclude functions on the $$slots property', function() {
7316+
var capturedTranscludeFn;
7317+
module(function() {
7318+
directive('trans', function() {
7319+
return {
7320+
transclude: true,
7321+
link: function(scope, element, attrs, controller, transclude) {
7322+
capturedTranscludeFn = transclude;
7323+
}
7324+
};
7325+
});
7326+
});
7327+
inject(function($rootScope, $compile, log) {
7328+
element = $compile(
7329+
'<div trans>' +
7330+
' <span ng-transclude-slot="minion">stuart</span>' +
7331+
' <span ng-transclude-slot="minion">bob</span>' +
7332+
' <span>dorothy</span>' +
7333+
' <span ng-transclude-slot="boss">gru</span>' +
7334+
'</div>')($rootScope);
7335+
$rootScope.$apply();
7336+
7337+
var minionTranscludeFn = capturedTranscludeFn.$$boundTransclude.$$slots['minion'];
7338+
var minions = minionTranscludeFn();
7339+
expect(minions[0].outerHTML).toEqual('<span ng-transclude-slot="minion" class="ng-scope">stuart</span>');
7340+
expect(minions[1].outerHTML).toEqual('<span ng-transclude-slot="minion" class="ng-scope">bob</span>');
7341+
7342+
var scope = element.scope();
7343+
7344+
var minionScope = jqLite(minions[0]).scope();
7345+
expect(minionScope.$parent).toBe(scope);
7346+
7347+
var bossTranscludeFn = capturedTranscludeFn.$$boundTransclude.$$slots['boss'];
7348+
var boss = bossTranscludeFn();
7349+
expect(boss[0].outerHTML).toEqual('<span ng-transclude-slot="boss" class="ng-scope">gru</span>');
7350+
7351+
var bossScope = jqLite(boss[0]).scope();
7352+
expect(bossScope.$parent).toBe(scope);
7353+
7354+
expect(bossScope).not.toBe(minionScope);
7355+
7356+
dealoc(boss);
7357+
dealoc(minions);
7358+
});
7359+
});
7360+
});
7361+
7362+
72657363
describe('img[src] sanitization', function() {
72667364

72677365
it('should NOT require trusted values for img src', inject(function($rootScope, $compile, $sce) {

0 commit comments

Comments
 (0)