From 70d0ba083a737e391e77409a386fdc76cff61824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20Galfas=C3=B3?= Date: Tue, 21 May 2013 19:32:47 -0300 Subject: [PATCH] fix($compile): reorder the capture of the directives with templateUrl When a directive defines a templateUrl, directives on the top level element will be applied after the directives on the original node even if the directive defines `replace: true` and `transclude: true` BREAKING CHANGE: * The compilation and linking order of directives that are defined on the top level element of an external template are compiled and linked after the directives of the original node --- src/ng/compile.js | 39 +++++++++++++++++++++++++++---------- test/ng/compileSpec.js | 44 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 68 insertions(+), 15 deletions(-) diff --git a/src/ng/compile.js b/src/ng/compile.js index 96529d3cdda9..cab4e885de3d 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -985,13 +985,14 @@ function $CompileProvider($provide) { }), templateUrl = (isFunction(origAsyncDirective.templateUrl)) ? origAsyncDirective.templateUrl($compileNode, tAttrs) - : origAsyncDirective.templateUrl; + : origAsyncDirective.templateUrl, + contentTemplateLinkFn; $compileNode.html(''); $http.get(templateUrl, {cache: $templateCache}). success(function(content) { - var compileNode, tempTemplateAttrs, $template; + var compileNode, tempTemplateAttrs, $template, contentDirectives; content = denormalizeTemplate(content); @@ -1005,7 +1006,7 @@ function $CompileProvider($provide) { tempTemplateAttrs = {$attr: {}}; replaceWith($rootElement, $compileNode, compileNode); - collectDirectives(compileNode, directives, tempTemplateAttrs); + contentDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); mergeTemplateAttributes(tAttrs, tempTemplateAttrs); } else { compileNode = beforeTemplateCompileNode; @@ -1014,9 +1015,11 @@ function $CompileProvider($provide) { directives.unshift(derivedSyncDirective); afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, childTranscludeFn); + if (contentDirectives) { + contentTemplateLinkFn = applyDirectivesToNode(contentDirectives, compileNode, tAttrs, childTranscludeFn); + } afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); - while(linkQueue.length) { var scope = linkQueue.shift(), beforeTemplateLinkNode = linkQueue.shift(), @@ -1030,9 +1033,17 @@ function $CompileProvider($provide) { replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); } - afterTemplateNodeLinkFn(function() { - beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, controller); - }, scope, linkNode, $rootElement, controller); + if (contentTemplateLinkFn) { + afterTemplateNodeLinkFn(function() { + beforeTemplateNodeLinkFn(function () { + contentTemplateLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, controller); + }, scope, linkNode, $rootElement, controller); + }, scope, linkNode, $rootElement, controller); + } else { + afterTemplateNodeLinkFn(function() { + beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, controller); + }, scope, linkNode, $rootElement, controller); + } } linkQueue = null; }). @@ -1047,9 +1058,17 @@ function $CompileProvider($provide) { linkQueue.push(rootElement); linkQueue.push(controller); } else { - afterTemplateNodeLinkFn(function() { - beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, controller); - }, scope, node, rootElement, controller); + if (contentTemplateLinkFn) { + afterTemplateNodeLinkFn(function() { + beforeTemplateNodeLinkFn(function () { + contentTemplateLinkFn(afterTemplateChildLinkFn, scope, node, $rootElement, controller); + }, scope, node, $rootElement, controller); + }, scope, node, $rootElement, controller); + } else { + afterTemplateNodeLinkFn(function() { + beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, $rootElement, controller); + }, scope, node, $rootElement, controller); + } } }; } diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index 26f61357bee3..87e64434c722 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -1072,8 +1072,8 @@ describe('$compile', function() { $httpBackend.flush(); $rootScope.$digest(); expect(log).toEqual( - 'iFirst-C; FLUSH; iSecond-C; iThird-C; iLast-C; ' + - 'iFirst-L; iSecond-L; iThird-L; iLast-L'); + 'iFirst-C; FLUSH; iSecond-C; iLast-C; iThird-C; ' + + 'iThird-L; iFirst-L; iSecond-L; iLast-L'); var div = element.find('div'); expect(div.attr('i-first')).toEqual(''); @@ -1117,13 +1117,13 @@ describe('$compile', function() { expect(log).toEqual('iFirst-C'); log('FLUSH'); $httpBackend.flush(); - expect(log).toEqual('iFirst-C; FLUSH; iSecond-C; iThird-C; iLast-C'); + expect(log).toEqual('iFirst-C; FLUSH; iSecond-C; iLast-C; iThird-C'); element = template($rootScope); $rootScope.$digest(); expect(log).toEqual( - 'iFirst-C; FLUSH; iSecond-C; iThird-C; iLast-C; ' + - 'iFirst-L; iSecond-L; iThird-L; iLast-L'); + 'iFirst-C; FLUSH; iSecond-C; iLast-C; iThird-C; ' + + 'iThird-L; iFirst-L; iSecond-L; iLast-L'); var div = element.find('div'); expect(div.attr('i-first')).toEqual(''); @@ -1981,6 +1981,31 @@ describe('$compile', function() { scope: { attr: 'xxx' } }; }); + directive('parentDirective', function() { + return { + restrict: 'E', + transclude: true, + replace: true, + templateUrl: 'parentDirective.html', + scope: { param: '=' }, + controller: function ($scope, $element, log) { log($scope.param); } + }; + }); + directive('childDirective', function() { + return { + restrict : 'E', + require: '^parentDirective', + scope: { otherParam: '@' }, + link: function ($scope, $elem, $attrs, parentDirective) { }, + replace : true, + templateUrl: 'childDirective.html', + controller : function($scope, $attrs, log) { log($scope.otherParam); } + }; + }); + })); + beforeEach(inject(function($templateCache) { + $templateCache.put('parentDirective.html', '
'); + $templateCache.put('childDirective.html', '{{otherParam}}'); })); describe('attribute', function() { @@ -2008,6 +2033,15 @@ describe('$compile', function() { expect(componentScope.attr).toEqual('hello igor'); expect(componentScope.attrAlias).toEqual('hello igor'); })); + it('should invoke the controller of the element directive before the controllers of the ' + + 'attribute directives at the top level element of a templateUrl', inject(function($rootScope, $compile, log) { + $rootScope.name = 'lucas'; + var element = $compile('
')($rootScope); + $rootScope.$apply(); + expect(element.text()).toBe(":-)"); + expect(log).toEqual('lucas; :-)'); + dealoc(element); + })); });