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

Commit 47fe7b5

Browse files
feat($compile): multiple transclusion via slots
1 parent c3a654b commit 47fe7b5

File tree

3 files changed

+142
-6
lines changed

3 files changed

+142
-6
lines changed

src/ng/compile.js

+29-2
Original file line numberDiff line numberDiff line change
@@ -1465,6 +1465,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
14651465
});
14661466
};
14671467

1468+
var boundSlots = boundTranscludeFn.$$slots = createMap();
1469+
for (var slotName in transcludeFn.$$slots) {
1470+
boundSlots[slotName] = createBoundTranscludeFn(scope, transcludeFn.$$slots[slotName], previousBoundTranscludeFn);
1471+
}
1472+
14681473
return boundTranscludeFn;
14691474
}
14701475

@@ -1625,6 +1630,28 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
16251630
};
16261631
}
16271632

1633+
1634+
function compileTemplate($templateNodes, parentTranscludeFn, terminalPriority, ignoreDirective, previousCompileContext) {
1635+
var defaultSlot = [];
1636+
var slots = createMap();
1637+
var slot;
1638+
forEach($templateNodes, function(node) {
1639+
var slotName = jqLite(node).attr('ng-transclude-slot');
1640+
var slot = defaultSlot;
1641+
if (slotName) {
1642+
slot = slots[slotName] = (slots[slotName] || []);
1643+
}
1644+
slot.push(node);
1645+
});
1646+
1647+
var transcludeFn = compile(defaultSlot, parentTranscludeFn, terminalPriority, ignoreDirective, previousCompileContext);
1648+
forEach(Object.keys(slots), function(slotName) {
1649+
slots[slotName] = compile(slots[slotName], parentTranscludeFn, terminalPriority, ignoreDirective, previousCompileContext);
1650+
});
1651+
transcludeFn.$$slots = slots;
1652+
return transcludeFn;
1653+
}
1654+
16281655
/**
16291656
* Once the directives have been collected, their compile functions are executed. This method
16301657
* is responsible for inlining directive templates as well as terminating the application
@@ -1740,7 +1767,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
17401767
compileNode = $compileNode[0];
17411768
replaceWith(jqCollection, sliceArgs($template), compileNode);
17421769

1743-
childTranscludeFn = compile($template, transcludeFn, terminalPriority,
1770+
childTranscludeFn = compileTemplate($template, transcludeFn, terminalPriority,
17441771
replaceDirective && replaceDirective.name, {
17451772
// Don't pass in:
17461773
// - controllerDirectives - otherwise we'll create duplicates controllers
@@ -1754,7 +1781,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
17541781
} else {
17551782
$template = jqLite(jqLiteClone(compileNode)).contents();
17561783
$compileNode.empty(); // clear contents
1757-
childTranscludeFn = compile($template, transcludeFn);
1784+
childTranscludeFn = compileTemplate($template, transcludeFn);
17581785
}
17591786
}
17601787

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
@@ -7039,6 +7039,104 @@ describe('$compile', function() {
70397039
});
70407040

70417041

7042+
describe('multi-slot transclude', function() {
7043+
it('should only include elements without `ng-transclude-slot` attribute in default transclusion function', function() {
7044+
module(function() {
7045+
directive('trans', function() {
7046+
return {
7047+
transclude: true,
7048+
template: '<div ng-transclude></div>'
7049+
};
7050+
});
7051+
});
7052+
inject(function($rootScope, $compile) {
7053+
element = $compile(
7054+
'<div trans>' +
7055+
'<span>stuart</span>' +
7056+
'<span>bob</span>' +
7057+
'<span ng-transclude-slot="boss">gru</span>' +
7058+
'<span>kevin</span>' +
7059+
'</div>')($rootScope);
7060+
$rootScope.$apply();
7061+
expect(element.text()).toEqual('stuartbobkevin');
7062+
});
7063+
});
7064+
7065+
it('should transclude elements to an `ng-transclude` with a matching `ng-transclude-slot`', function() {
7066+
module(function() {
7067+
directive('trans', function() {
7068+
return {
7069+
transclude: true,
7070+
template:
7071+
'<div class="boss" ng-transclude="boss"></div>' +
7072+
'<div class="minion" ng-transclude="minion"></div>' +
7073+
'<div class="other" ng-transclude></div>'
7074+
};
7075+
});
7076+
});
7077+
inject(function($rootScope, $compile) {
7078+
element = $compile(
7079+
'<div trans>' +
7080+
'<span ng-transclude-slot="minion">stuart</span>' +
7081+
'<span>dorothy</span>' +
7082+
'<span ng-transclude-slot="boss">gru</span>' +
7083+
'<span ng-transclude-slot="minion">kevin</span>' +
7084+
'</div>')($rootScope);
7085+
$rootScope.$apply();
7086+
expect(element.children().eq(0).text()).toEqual('gru');
7087+
expect(element.children().eq(1).text()).toEqual('stuartkevin');
7088+
expect(element.children().eq(2).text()).toEqual('dorothy');
7089+
});
7090+
});
7091+
7092+
it('should provide the elements marked with `ng-transclude-slot` as additional transclude functions on the $$slots property', function() {
7093+
var capturedTranscludeFn;
7094+
module(function() {
7095+
directive('trans', function() {
7096+
return {
7097+
transclude: true,
7098+
link: function(scope, element, attrs, controller, transclude) {
7099+
capturedTranscludeFn = transclude;
7100+
}
7101+
};
7102+
});
7103+
});
7104+
inject(function($rootScope, $compile, log) {
7105+
element = $compile(
7106+
'<div trans>' +
7107+
' <span ng-transclude-slot="minion">stuart</span>' +
7108+
' <span ng-transclude-slot="minion">bob</span>' +
7109+
' <span>dorothy</span>' +
7110+
' <span ng-transclude-slot="boss">gru</span>' +
7111+
'</div>')($rootScope);
7112+
$rootScope.$apply();
7113+
7114+
var minionTranscludeFn = capturedTranscludeFn.$$boundTransclude.$$slots['minion'];
7115+
var minions = minionTranscludeFn();
7116+
expect(minions[0].outerHTML).toEqual('<span ng-transclude-slot="minion" class="ng-scope">stuart</span>');
7117+
expect(minions[1].outerHTML).toEqual('<span ng-transclude-slot="minion" class="ng-scope">bob</span>');
7118+
7119+
var scope = element.scope();
7120+
7121+
var minionScope = jqLite(minions[0]).scope();
7122+
expect(minionScope.$parent).toBe(scope);
7123+
7124+
var bossTranscludeFn = capturedTranscludeFn.$$boundTransclude.$$slots['boss'];
7125+
var boss = bossTranscludeFn();
7126+
expect(boss[0].outerHTML).toEqual('<span ng-transclude-slot="boss" class="ng-scope">gru</span>');
7127+
7128+
var bossScope = jqLite(boss[0]).scope();
7129+
expect(bossScope.$parent).toBe(scope);
7130+
7131+
expect(bossScope).not.toBe(minionScope);
7132+
7133+
dealoc(boss);
7134+
dealoc(minions);
7135+
});
7136+
});
7137+
});
7138+
7139+
70427140
describe('img[src] sanitization', function() {
70437141

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

0 commit comments

Comments
 (0)