From f1948e2191a6cbb03bc479b828fd323d47843541 Mon Sep 17 00:00:00 2001 From: Georgios Kalpakas Date: Thu, 29 Dec 2016 20:21:00 +0200 Subject: [PATCH] fix($compile): support transcluding multi-element directives Previously, transcluding multi-element directives (e.g. `foo-start`/`foo-end`) was not supported on elements with multi-slot transclusion (a `uterdir` error was thrown). This commit fixes it by putting the transcluded nodes into a DocumentFragment, where they can be traversed via `.nextSibling`. Fixes #15554 --- src/ng/compile.js | 12 +++++++----- test/ng/compileSpec.js | 44 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/src/ng/compile.js b/src/ng/compile.js index e8bd8ca01668..a257c0b5f096 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -2575,7 +2575,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { // We have transclusion slots, // collect them up, compile them and store their transclusion functions - $template = []; + $template = window.document.createDocumentFragment(); var slotMap = createMap(); var filledSlots = createMap(); @@ -2603,10 +2603,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { var slotName = slotMap[directiveNormalize(nodeName_(node))]; if (slotName) { filledSlots[slotName] = true; - slots[slotName] = slots[slotName] || []; - slots[slotName].push(node); + slots[slotName] = slots[slotName] || window.document.createDocumentFragment(); + slots[slotName].appendChild(node); } else { - $template.push(node); + $template.appendChild(node); } }); @@ -2620,9 +2620,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { for (var slotName in slots) { if (slots[slotName]) { // Only define a transclusion function if the slot was filled - slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slots[slotName], transcludeFn); + slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slots[slotName].childNodes, transcludeFn); } } + + $template = $template.childNodes; } $compileNode.empty(); // clear contents diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index d24a90f9744c..a0b8b7204439 100644 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -8843,6 +8843,50 @@ describe('$compile', function() { }); }); + + it('should correctly handle multi-element directives', function() { + module(function() { + directive('foo', valueFn({ + template: '[
]', + transclude: true + })); + directive('bar', valueFn({ + template: '[
|
]', + transclude: { + header: 'header', + footer: 'footer' + } + })); + }); + + inject(function($compile, $rootScope) { + var tmplWithFoo = + '' + + '
Hello,
' + + '
world!
' + + '
'; + var tmplWithBar = + '' + + '
This is a
' + + '
header!
' + + '
This is a
' + + '' + + '
'; + + var elem1 = $compile(tmplWithFoo)($rootScope); + var elem2 = $compile(tmplWithBar)($rootScope); + + $rootScope.$digest(); + + expect(elem1.text()).toBe('[Hello, world!]'); + expect(elem2.text()).toBe('[This is a header!|This is a footer!]'); + + dealoc(elem1); + dealoc(elem2); + }); + }); + + //see issue https://github.com/angular/angular.js/issues/12936 it('should use the proper scope when it is on the root element of a replaced directive template', function() { module(function() {