From 878c877a573be1b35af8c2a529f8957d81d08754 Mon Sep 17 00:00:00 2001 From: Ovidiu Dolha Date: Sat, 12 Mar 2016 19:08:18 +0200 Subject: [PATCH 1/2] feat($compile): support dynamic transclusion slots Make the 1.5+ multi-slot transclusion even more powerful. Allow the slots to be defined dynamically by component consumers, so components become more abstract, while template injection is clean and flexible. --- src/ng/compile.js | 20 +++++++- src/ng/directive/ngTransclude.js | 50 ++++++++++++++++++++ test/ng/compileSpec.js | 80 ++++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+), 2 deletions(-) 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..70bb03000496 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() { From 59a39fb0a9b752d104981cc88a8ebcc23f179182 Mon Sep 17 00:00:00 2001 From: Ovidiu Dolha Date: Wed, 16 Mar 2016 18:41:40 +0200 Subject: [PATCH 2/2] style: removed spaces not allowed (jscs) --- test/ng/compileSpec.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index 70bb03000496..e406b5f01ebc 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -8844,9 +8844,9 @@ describe('$compile', function() { }); describe('dynamic multi-slot transclude', function() { - it('should allow passing the transclude slots configuration - simple', function () { - module(function () { - directive('minionComponent', function () { + it('should allow passing the transclude slots configuration - simple', function() { + module(function() { + directive('minionComponent', function() { return { restrict: 'E', transclude: 'dynamic', @@ -8857,7 +8857,7 @@ describe('$compile', function() { }; }); }); - inject(function ($rootScope, $compile) { + inject(function($rootScope, $compile) { element = $compile( '' + 'stuart' + @@ -8870,9 +8870,9 @@ describe('$compile', function() { }); }); - it('should allow passing the transclude slots configuration - complex usage', function () { - module(function () { - directive('minionComponent', function () { + it('should allow passing the transclude slots configuration - complex usage', function() { + module(function() { + directive('minionComponent', function() { return { restrict: 'E', transclude: 'dynamic', @@ -8883,7 +8883,7 @@ describe('$compile', function() { }; }); }); - inject(function ($rootScope, $compile) { + inject(function($rootScope, $compile) { element = $compile( '' + 'stuart' + @@ -8897,8 +8897,8 @@ describe('$compile', function() { }); it('should error if slot config is not provided when using the directive', function() { - module(function () { - directive('minionComponent', function () { + module(function() { + directive('minionComponent', function() { return { restrict: 'E', transclude: 'dynamic', @@ -8909,7 +8909,7 @@ describe('$compile', function() { }; }); }); - inject(function ($rootScope, $compile) { + inject(function($rootScope, $compile) { expect(function() { element = $compile( '' +