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
337338 * The contents are compiled and provided to the directive as a **transclusion function**. See the
338339 * {@link $compile#transclusion Transclusion} section below.
339340 *
340- * There are two kinds of transclusion depending upon whether you want to transclude just the contents of the
341- * directive's element or the entire element:
341+ * There are three kinds of transclusion depending upon whether you want to transclude just the contents of the
342+ * directive's element, the entire element or parts of the element:
342343 *
343344 * * `true` - transclude the content (i.e. the child nodes) of the directive's element.
344345 * * `'element'` - transclude the whole of the directive's element including any directives on this
345346 * element that defined at a lower priority than this directive. When used, the `template`
346347 * property is ignored.
348+ * * **`{...}` (an object hash):** - map elements of the content onto transclusion "slots" in the template.
349+ * See {@link ngTransclude} for more information.
350+ *
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.
347354 *
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.
348357 *
349358 * #### `compile`
350359 *
@@ -1511,7 +1520,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
15111520 // so that they are available inside the `controllersBoundTransclude` function
15121521 var boundSlots = boundTranscludeFn . $$slots = createMap ( ) ;
15131522 for ( var slotName in transcludeFn . $$slots ) {
1514- boundSlots [ slotName ] = createBoundTranscludeFn ( scope , transcludeFn . $$slots [ slotName ] , previousBoundTranscludeFn ) ;
1523+ if ( transcludeFn . $$slots [ slotName ] ) {
1524+ boundSlots [ slotName ] = createBoundTranscludeFn ( scope , transcludeFn . $$slots [ slotName ] , previousBoundTranscludeFn ) ;
1525+ } else {
1526+ boundSlots [ slotName ] = null ;
1527+ }
15151528 }
15161529
15171530 return boundTranscludeFn ;
@@ -1855,32 +1868,42 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
18551868 } else {
18561869
18571870 var slots = createMap ( ) ;
1871+
18581872 $template = jqLite ( jqLiteClone ( compileNode ) ) . contents ( ) ;
18591873
18601874 if ( isObject ( directiveValue ) ) {
18611875
1862- // We have transclusion slots - collect them up and compile them and store their
1863- // transclusion functions
1876+ // We have transclusion slots,
1877+ // collect them up, compile them and store their transclusion functions
18641878 $template = [ ] ;
1865- var slotNames = createMap ( ) ;
1879+
1880+ var slotMap = createMap ( ) ;
18661881 var filledSlots = createMap ( ) ;
18671882
1868- // Parse the slot names: if they start with a ? then they are optional
1869- forEach ( directiveValue , function ( slotName , key ) {
1870- var optional = ( slotName . charAt ( 0 ) === '?' ) ;
1871- slotName = optional ? slotName . substring ( 1 ) : slotName ;
1872- slotNames [ key ] = slotName ;
1873- slots [ slotName ] = [ ] ;
1883+ // Parse the element selectors
1884+ forEach ( directiveValue , function ( elementSelector , slotName ) {
1885+ // If an element selector starts with a ? then it is optional
1886+ var optional = ( elementSelector . charAt ( 0 ) === '?' ) ;
1887+ elementSelector = optional ? elementSelector . substring ( 1 ) : elementSelector ;
1888+
1889+ slotMap [ elementSelector ] = slotName ;
1890+
1891+ // We explicitly assign `null` since this implies that a slot was defined but not filled.
1892+ // Later when calling boundTransclusion functions with a slot name we only error if the
1893+ // slot is `undefined`
1894+ slots [ slotName ] = null ;
1895+
18741896 // filledSlots contains `true` for all slots that are either optional or have been
18751897 // filled. This is used to check that we have not missed any required slots
18761898 filledSlots [ slotName ] = optional ;
18771899 } ) ;
18781900
18791901 // Add the matching elements into their slot
18801902 forEach ( $compileNode . contents ( ) , function ( node ) {
1881- var slotName = slotNames [ directiveNormalize ( nodeName_ ( node ) ) ] ;
1903+ var slotName = slotMap [ nodeName_ ( node ) ] ;
18821904 if ( slotName ) {
18831905 filledSlots [ slotName ] = true ;
1906+ slots [ slotName ] = slots [ slotName ] || [ ] ;
18841907 slots [ slotName ] . push ( node ) ;
18851908 } else {
18861909 $template . push ( node ) ;
@@ -1894,9 +1917,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
18941917 }
18951918 } ) ;
18961919
1897- forEach ( Object . keys ( slots ) , function ( slotName ) {
1898- slots [ slotName ] = compilationGenerator ( mightHaveMultipleTransclusionError , slots [ slotName ] , transcludeFn ) ;
1899- } ) ;
1920+ for ( var slotName in slots ) {
1921+ if ( slots [ slotName ] ) {
1922+ // Only define a transclusion function if the slot was filled
1923+ slots [ slotName ] = compilationGenerator ( mightHaveMultipleTransclusionError , slots [ slotName ] , transcludeFn ) ;
1924+ }
1925+ }
19001926 }
19011927
19021928 $compileNode . empty ( ) ; // clear contents
@@ -2125,6 +2151,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
21252151 // is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
21262152 transcludeFn = controllersBoundTransclude ;
21272153 transcludeFn . $$boundTransclude = boundTranscludeFn ;
2154+ // expose the slots on the `$transclude` function
2155+ transcludeFn . $slots = boundTranscludeFn . $$slots ;
21282156 }
21292157
21302158 if ( controllerDirectives ) {
@@ -2221,16 +2249,22 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
22212249 futureParentElement = hasElementTranscludeDirective ? $element . parent ( ) : $element ;
22222250 }
22232251 if ( slotName ) {
2252+ // slotTranscludeFn can be one of three things:
2253+ // * a transclude function - a filled slot
2254+ // * `null` - an optional slot that was not filled
2255+ // * `undefined` - a slot that was not declared (i.e. invalid)
22242256 var slotTranscludeFn = boundTranscludeFn . $$slots [ slotName ] ;
2225- if ( ! slotTranscludeFn ) {
2257+ if ( slotTranscludeFn ) {
2258+ return slotTranscludeFn ( scope , cloneAttachFn , transcludeControllers , futureParentElement , scopeToChild ) ;
2259+ } else if ( isUndefined ( slotTranscludeFn ) ) {
22262260 throw $compileMinErr ( 'noslot' ,
22272261 'No parent directive that requires a transclusion with slot name "{0}". ' +
22282262 'Element: {1}' ,
22292263 slotName , startingTag ( $element ) ) ;
22302264 }
2231- return slotTranscludeFn ( scope , cloneAttachFn , transcludeControllers , futureParentElement , scopeToChild ) ;
2265+ } else {
2266+ return boundTranscludeFn ( scope , cloneAttachFn , transcludeControllers , futureParentElement , scopeToChild ) ;
22322267 }
2233- return boundTranscludeFn ( scope , cloneAttachFn , transcludeControllers , futureParentElement , scopeToChild ) ;
22342268 }
22352269 }
22362270 }
0 commit comments