From 15e1a29cd08993b599f390e83a249ec17f753972 Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Wed, 13 Mar 2013 16:29:26 -0700 Subject: [PATCH] fix(compiler): corrects component transclusion on compilation root. Closes# 2155 --- src/ng/compile.js | 20 ++++--- test/ng/compileSpec.js | 10 ++-- test/ng/directive/ngRepeatSpec.js | 95 ++++++++++++++++++++++++++++++- 3 files changed, 112 insertions(+), 13 deletions(-) diff --git a/src/ng/compile.js b/src/ng/compile.js index caa4e8ce547e..36043a15bc36 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -749,7 +749,7 @@ function $CompileProvider($provide) { newTemplateAttrs ) ); - mergeTemplateAttributes(templateAttrs, newTemplateAttrs); + mergeTemplateAttributes(templateAttrs, newTemplateAttrs, directive.name); ii = directives.length; } else { @@ -1007,15 +1007,16 @@ function $CompileProvider($provide) { * * @param {object} dst destination attributes (original DOM) * @param {object} src source attributes (from the directive template) + * @param {string} ignoreName attribute which should be ignored */ - function mergeTemplateAttributes(dst, src) { + function mergeTemplateAttributes(dst, src, ignoreName) { var srcAttr = src.$attr, dstAttr = dst.$attr, $element = dst.$$element; // reapply the old attributes to the new element forEach(dst, function(value, key) { - if (key.charAt(0) != '$') { + if (key.charAt(0) != '$' && key != ignoreName) { if (src[key]) { value += (key === 'style' ? ';' : ' ') + src[key]; } @@ -1030,7 +1031,7 @@ function $CompileProvider($provide) { dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value; } else if (key == 'style') { $element.attr('style', $element.attr('style') + ';' + value); - } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) { + } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key) && key != ignoreName) { dst[key] = value; dstAttr[key] = srcAttr[key]; } @@ -1073,14 +1074,19 @@ function $CompileProvider($provide) { tempTemplateAttrs = {$attr: {}}; replaceWith($rootElement, $compileNode, compileNode); collectDirectives(compileNode, directives, tempTemplateAttrs); - mergeTemplateAttributes(tAttrs, tempTemplateAttrs); + mergeTemplateAttributes(tAttrs, tempTemplateAttrs, origAsyncDirective.name); } else { compileNode = beforeTemplateCompileNode; $compileNode.html(content); } directives.unshift(derivedSyncDirective); - afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, childTranscludeFn); + afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, childTranscludeFn, $compileNode); + forEach($rootElement, function(node, i) { + if (node == compileNode) { + $rootElement[i] = $compileNode[0]; + } + }); afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); @@ -1089,7 +1095,7 @@ function $CompileProvider($provide) { beforeTemplateLinkNode = linkQueue.shift(), linkRootElement = linkQueue.shift(), controller = linkQueue.shift(), - linkNode = compileNode; + linkNode = $compileNode[0]; if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { // it was cloned therefore we have to clone as well. diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index 672a704d1154..0ef7e8bf460d 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -534,7 +534,7 @@ describe('$compile', function() { expect(div.hasClass('log')).toBe(true); expect(div.css('width')).toBe('10px'); expect(div.css('height')).toBe('20px'); - expect(div.attr('replace')).toEqual(''); + expect(div.attr('replace')).toEqual(undefined); expect(div.attr('high-log')).toEqual(''); })); @@ -856,7 +856,7 @@ describe('$compile', function() { $rootScope.$digest(); expect(sortedHtml(element)). - toEqual('
Hello, Elvis!
'); + toEqual('
Hello, Elvis!
'); })); @@ -868,7 +868,7 @@ describe('$compile', function() { $rootScope.$digest(); expect(sortedHtml(element)). - toEqual('Hello, Elvis!'); + toEqual('Hello, Elvis!'); })); @@ -1077,7 +1077,7 @@ describe('$compile', function() { var div = element.find('div'); expect(div.attr('i-first')).toEqual(''); - expect(div.attr('i-second')).toEqual(''); + expect(div.attr('i-second')).toEqual(undefined); expect(div.attr('i-third')).toEqual(''); expect(div.attr('i-last')).toEqual(''); @@ -1127,7 +1127,7 @@ describe('$compile', function() { var div = element.find('div'); expect(div.attr('i-first')).toEqual(''); - expect(div.attr('i-second')).toEqual(''); + expect(div.attr('i-second')).toEqual(undefined); expect(div.attr('i-third')).toEqual(''); expect(div.attr('i-last')).toEqual(''); diff --git a/test/ng/directive/ngRepeatSpec.js b/test/ng/directive/ngRepeatSpec.js index 9fc445bad19b..925a93ce4737 100644 --- a/test/ng/directive/ngRepeatSpec.js +++ b/test/ng/directive/ngRepeatSpec.js @@ -1,7 +1,11 @@ 'use strict'; describe('ngRepeat', function() { - var element, $compile, scope, $exceptionHandler; + var element, $compile, scope, $exceptionHandler, $compileProvider; + + beforeEach(module(function(_$compileProvider_) { + $compileProvider = _$compileProvider_; + })); beforeEach(module(function($exceptionHandlerProvider) { @@ -448,6 +452,95 @@ describe('ngRepeat', function() { }); + describe('nesting in replaced directive templates', function() { + + it('should work when placed on a root element of attr directive with SYNC replaced template', + inject(function($templateCache, $compile, $rootScope) { + $compileProvider.directive('replaceMeWithRepeater', function() { + return { + replace: true, + template: '{{log(i)}}' + } + }); + element = jqLite(''); + $compile(element)($rootScope); + expect(element.text()).toBe(''); + var logs = []; + $rootScope.log = function(t) { logs.push(t); }; + + // This creates one item, but it has no parent so we can't get to it + $rootScope.items = [1, 2]; + $rootScope.$apply(); + + // This cleans up to prevent memory leak + $rootScope.items = []; + $rootScope.$apply(); + expect(angular.mock.dump(element)).toBe(''); + expect(logs).toEqual([1, 2, 1, 2]); + })); + + + iit('should work when placed on a root element of attr directive with ASYNC replaced template', + inject(function($templateCache, $compile, $rootScope) { + $compileProvider.directive('replaceMeWithRepeater', function() { + return { + replace: true, + templateUrl: 'replace-me-with-repeater.html' + } + }); + $templateCache.put('replace-me-with-repeater.html', '
{{log(i)}}
'); + element = jqLite('--'); + $compile(element)($rootScope); + expect(element.text()).toBe('--'); + var logs = []; + $rootScope.log = function(t) { logs.push(t); }; + + // This creates one item, but it has no parent so we can't get to it + $rootScope.items = [1, 2]; + $rootScope.$apply(); + + // This cleans up to prevent memory leak + $rootScope.items = []; + $rootScope.$apply(); + expect(sortedHtml(element)).toBe('--'); + expect(logs).toEqual([1, 2, 1, 2]); + })); + + + it('should work when placed on a root element of element directive with SYNC replaced template', + inject(function($templateCache, $compile, $rootScope) { + $compileProvider.directive('replaceMeWithRepeater', function() { + return { + restrict: 'E', + replace: true, + template: '
{{i}}
' + } + }); + element = $compile('
')($rootScope); + expect(element.text()).toBe(''); + $rootScope.$apply(); + expect(element.text()).toBe('123'); + })); + + + it('should work when placed on a root element of element directive with ASYNC replaced template', + inject(function($templateCache, $compile, $rootScope) { + $compileProvider.directive('replaceMeWithRepeater', function() { + return { + restrict: 'E', + replace: true, + templateUrl: 'replace-me-with-repeater.html' + } + }); + $templateCache.put('replace-me-with-repeater.html', '
{{i}}
'); + element = $compile('
')($rootScope); + expect(element.text()).toBe(''); + $rootScope.$apply(); + expect(element.text()).toBe('123'); + })); + }); + + describe('stability', function() { var a, b, c, d, lis;