diff --git a/src/ng/compile.js b/src/ng/compile.js index 18ecce99dc92..7b9acc6cd026 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -536,14 +536,16 @@ * Testing Transclusion Directives}. * * - * There are three kinds of transclusion depending upon whether you want to transclude just the contents of the - * directive's element, the entire element or multiple parts of the element contents: + * There are four kinds of transclusion depending upon whether you want to transclude just the contents of the + * directive's element, the entire element, multiple parts of the element contents, or multiple parts dynamically: * * * `true` - transclude the content (i.e. the child nodes) of the directive's element. * * `'element'` - transclude the whole of the directive's element including any directives on this * element that defined at a lower priority than this directive. When used, the `template` * property is ignored. * * **`{...}` (an object hash):** - map elements of the content onto transclusion "slots" in the template. + * * `'dynamic'` - allow dynamic mulit-slot transclusion, by providing the slot configuration when using the component + * (in the transclusion-slot attrbiute) * * **Mult-slot transclusion** is declared by providing an object for the `transclude` property. * @@ -563,6 +565,13 @@ * `$transclude.isSlotFilled(slotName)` on the transclude function passed to the directive's link function and * injectable into the directive's controller. * + * **Dynamic mult-slot transclusion** is declared by using the 'dynamic' value on the component and then passing the + * transclusion slot configuration "later" (when actually using the component - via the transclude-slots attribute). + * Using this mode requires clear contract agreement between the component and the consumers, but it enables the + * possibility of arbitrary slots being used (in number and names). + * + * For an example of the dynamic mode usage, see {@link ngTransclude#dynamic-multi-slot-transclusion}. + * * * #### Transclusion Functions * @@ -2085,6 +2094,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { $template = jqLite(jqLiteClone(compileNode)).contents(); + if (directiveValue === 'dynamic') { + if (!templateAttrs.transcludeSlots) { + throw $compileMinErr('reqdyn', 'Directive `{0}` requests dynamic transclusion slots but are not provided.', directive.name); + } + directiveValue = $parse(templateAttrs.transcludeSlots)(); + } + if (isObject(directiveValue)) { // We have transclusion slots, diff --git a/src/ng/directive/ngTransclude.js b/src/ng/directive/ngTransclude.js index c55537b2fdd0..f7555162587a 100644 --- a/src/ng/directive/ngTransclude.js +++ b/src/ng/directive/ngTransclude.js @@ -155,6 +155,56 @@ * expect(element(by.binding('text')).getText()).toEqual('TEXT'); * expect(element(by.css('.footer')).getText()).toEqual('Fallback Footer'); * }); + * + * + * + * @example + * ### Dynamic multi-slot transclusion + * This example demonstrates using dynamic approach with multi-slot transclusion in a component directive. + * In this example, we use this mode to define a custom table component, while allowing the consumers to cleanly but + * optionally define their own cell templates: + * + * + * + *
+ * + * {{$parent.row['firstName']}} + * {{$parent.row['lastName'].toUpperCase()}} + * + *
+ *
+ * + * angular.module('dynamicMultiSlotTranscludeExample', []) + * .directive('myTable', function() { + * return { + * restrict: 'E', + * transclude: 'dynamic', + * template: '' + + * '' + + * '' + + * '' + + * '' + + * '' + + * '' + + * '' + + * '' + + * '
{{header.caption}}
' + + * '
' + + * '
{{row[header.name]}}
' + + * '
', + * scope: { + * transcludeSlots: '=', + * headers: '=', + * data: '=' + * } + * }; +* }); *
*
*/ diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index 999830843ca0..e406b5f01ebc 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -8843,6 +8843,86 @@ describe('$compile', function() { }); }); + describe('dynamic multi-slot transclude', function() { + it('should allow passing the transclude slots configuration - simple', function() { + module(function() { + directive('minionComponent', function() { + return { + restrict: 'E', + transclude: 'dynamic', + scope: { + transcludeSlots: '<' + }, + template: '
' + }; + }); + }); + inject(function($rootScope, $compile) { + element = $compile( + '' + + 'stuart' + + 'kevin' + + 'bob' + + 'gru' + + '')($rootScope); + $rootScope.$apply(); + expect(element.text()).toEqual('bob'); + }); + }); + + it('should allow passing the transclude slots configuration - complex usage', function() { + module(function() { + directive('minionComponent', function() { + return { + restrict: 'E', + transclude: 'dynamic', + scope: { + transcludeSlots: '<' + }, + template: '
!
' + }; + }); + }); + inject(function($rootScope, $compile) { + element = $compile( + '' + + 'stuart' + + 'kevin' + + 'bob' + + 'gru' + + '')($rootScope); + $rootScope.$apply(); + expect(element.text()).toEqual('bob!gru!kevin!stuart!'); + }); + }); + + it('should error if slot config is not provided when using the directive', function() { + module(function() { + directive('minionComponent', function() { + return { + restrict: 'E', + transclude: 'dynamic', + scope: { + transcludeSlots: '<' + }, + template: '
!
' + }; + }); + }); + inject(function($rootScope, $compile) { + expect(function() { + element = $compile( + '' + + 'stuart' + + 'kevin' + + 'bob' + + 'gru' + + '')($rootScope); + }).toThrowMinErr('$compile', 'reqdyn', + 'Directive `minionComponent` requests dynamic transclusion slots but are not provided.'); + }); + }); + }); describe('img[src] sanitization', function() {