Skip to content

Commit 6d52396

Browse files
feat($compile): expose transclusion $slots on the $transclude function
Unfilled optional slots will exist as properties of `$slots` but have a value of `null`. Closes angular#13426
1 parent 4c6b500 commit 6d52396

File tree

2 files changed

+24
-11
lines changed

2 files changed

+24
-11
lines changed

src/ng/compile.js

+18-5
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,8 @@
237237
* as those elements need to created and cloned in a special way when they are defined outside their
238238
* usual containers (e.g. like `<svg>`).
239239
* * See also the `directive.templateNamespace` property.
240-
*
240+
* The `$transclude` function has a property called `$slots`, which is a hash of slot names to slot transclusion
241+
* functions. If a slot was declared but not filled its value on the `$slots` object will be `null`.
241242
*
242243
* #### `require`
243244
* Require another directive and inject its controller as the fourth argument to the linking function. The
@@ -347,6 +348,13 @@
347348
* * **`{...}` (an object hash):** - map elements of the content onto transclusion "slots" in the template.
348349
* See {@link ngTransclude} for more information.
349350
*
351+
* Mult-slot transclusion is declared by providing an object for the `transclude` property.
352+
* This object is a map where the keys are the canonical name of HTML elements to match in the transcluded HTML,
353+
* and the values are the names of the slots. If the name is prefixed with a `?` then that slot is optional.
354+
*
355+
* The slots are made available as `$transclude.$slots` on the transclude function that is passed to the
356+
* linking functions as the fifth parameter, and can be injected into the directive controller.
357+
*
350358
* #### `compile`
351359
*
352360
* ```js
@@ -2137,7 +2145,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
21372145
// is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
21382146
transcludeFn = controllersBoundTransclude;
21392147
transcludeFn.$$boundTransclude = boundTranscludeFn;
2140-
transcludeFn.$$slots = boundTranscludeFn.$$slots;
2148+
// expose the slots on the `$transclude` function
2149+
transcludeFn.$slots = boundTranscludeFn.$$slots;
21412150
}
21422151

21432152
if (controllerDirectives) {
@@ -2234,18 +2243,22 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
22342243
futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element;
22352244
}
22362245
if (slotName) {
2246+
// slotTranscludeFn can be one of three things:
2247+
// * a transclude function - a filled slot
2248+
// * `null` - an optional slot that was not filled
2249+
// * `undefined` - a slot that was not declared (i.e. invalid)
22372250
var slotTranscludeFn = boundTranscludeFn.$$slots[slotName];
22382251
if (slotTranscludeFn) {
22392252
return slotTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
2240-
}
2241-
if (isUndefined(slotTranscludeFn)) {
2253+
} else if (isUndefined(slotTranscludeFn)) {
22422254
throw $compileMinErr('noslot',
22432255
'No parent directive that requires a transclusion with slot name "{0}". ' +
22442256
'Element: {1}',
22452257
slotName, startingTag($element));
22462258
}
2259+
} else {
2260+
return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
22472261
}
2248-
return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
22492262
}
22502263
}
22512264
}

test/ng/compileSpec.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -7918,7 +7918,7 @@ describe('$compile', function() {
79187918
});
79197919

79207920

7921-
it('should provide the elements marked with matching transclude elements as additional transclude functions on the $$slots property', function() {
7921+
it('should provide the elements marked with matching transclude elements as additional transclude functions on the $slots property', function() {
79227922
var capturedTranscludeFn;
79237923
module(function() {
79247924
directive('minionComponent', function() {
@@ -7949,7 +7949,7 @@ describe('$compile', function() {
79497949
'</minion-component>')($rootScope);
79507950
$rootScope.$apply();
79517951

7952-
var minionTranscludeFn = capturedTranscludeFn.$$slots['minionSlot'];
7952+
var minionTranscludeFn = capturedTranscludeFn.$slots['minionSlot'];
79537953
var minions = minionTranscludeFn();
79547954
expect(minions[0].outerHTML).toEqual('<minion class="ng-scope">stuart</minion>');
79557955
expect(minions[1].outerHTML).toEqual('<minion class="ng-scope">bob</minion>');
@@ -7959,7 +7959,7 @@ describe('$compile', function() {
79597959
var minionScope = jqLite(minions[0]).scope();
79607960
expect(minionScope.$parent).toBe(scope);
79617961

7962-
var bossTranscludeFn = capturedTranscludeFn.$$slots['bossSlot'];
7962+
var bossTranscludeFn = capturedTranscludeFn.$slots['bossSlot'];
79637963
var boss = bossTranscludeFn();
79647964
expect(boss[0].outerHTML).toEqual('<boss class="ng-scope">gru</boss>');
79657965

@@ -7973,7 +7973,7 @@ describe('$compile', function() {
79737973
});
79747974
});
79757975

7976-
it('should set unfilled optional transclude slots to `null` in the $transclude.$$slots property', function() {
7976+
it('should set unfilled optional transclude slots to `null` in the $transclude.$slots property', function() {
79777977
var capturedTranscludeFn;
79787978
module(function() {
79797979
directive('minionComponent', function() {
@@ -7996,8 +7996,8 @@ describe('$compile', function() {
79967996
'<minion>stuart</minion>' +
79977997
'<span>dorothy</span>' +
79987998
'</minion-component>')($rootScope);
7999-
expect(capturedTranscludeFn.$$slots.minionSlot).toEqual(jasmine.any(Function));
8000-
expect(capturedTranscludeFn.$$slots.bossSlot).toBe(null);
7999+
expect(capturedTranscludeFn.$slots.minionSlot).toEqual(jasmine.any(Function));
8000+
expect(capturedTranscludeFn.$slots.bossSlot).toBe(null);
80018001
});
80028002
});
80038003

0 commit comments

Comments
 (0)