Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 742271f

Browse files
vojtajinaIgorMinar
authored andcommitted
fix($compile): link parents before traversing
How did compiling a templateUrl (async) directive with `replace:true` work before this commit? 1/ apply all directives with higher priority than the templateUrl directive 2/ partially apply the templateUrl directive (create `beforeTemplateNodeLinkFn`) 3/ fetch the template 4/ apply second part of the templateUrl directive on the fetched template (`afterTemplateNodeLinkFn`) That is, the templateUrl directive is basically split into two parts (two `nodeLinkFn` functions), which has to be both applied. Normally we compose linking functions (`nodeLinkFn`) using continuation - calling the linking function of a parent element, passing the linking function of the child elements as an argument. The parent linking function then does: 1/ execute its pre-link functions 2/ call the child elements linking function (traverse) 3/ execute its post-link functions Now, we have two linking functions for the same DOM element level (because the templateUrl directive has been split). There has been multiple issues because of the order of these two linking functions (creating controller before setting up scope locals, running linking functions before instantiating controller, etc.). It is easy to fix one use case, but it breaks some other use case. It is hard to decide what is the "correct" order of these two linking functions as they are essentially on the same level. Running them side-by-side screws up pre/post linking functions for the high priority directives (those executed before the templateUrl directive). It runs post-linking functions before traversing: ```js beforeTemplateNodeLinkFn(null); // do not travers afterTemplateNodeLinkFn(afterTemplateChildLinkFn); ``` Composing them (in any order) screws up the order of post-linking functions. We could fix this by having post-linking functions to execute in reverse order (from the lowest priority to the highest) which might actually make a sense. **My solution is to remove this splitting.** This commit removes the `beforeTemplateNodeLinkFn`. The first run (before we have the template) only schedules fetching the template. The rest (creating scope locals, instantiating a controller, linking functions, etc) is done when processing the directive again (in the context of the already fetched template; this is the cloned `derivedSyncDirective`). We still need to pass-through the linking functions of the higher priority directives (those executed before the templateUrl directive), that's why I added `preLinkFns` and `postLinkFns` arguments to `applyDirectivesToNode`. This also changes the "$compile transclude should make the result of a transclusion available to the parent directive in post- linking phase (templateUrl)" unit test. It was testing that a parent directive can see the content of transclusion in its pre-link function. That is IMHO wrong (as the `ngTransclude` directive inserts the translusion in its linking function). This test was only passing because of c173ca4, which changed the behavior of the compiler to traverse before executing the parent linking function. That was wrong and also caused the #3792 issue, which this change fixes. Closes #3792 Closes #3923 Closes #3935 Closes #3927
1 parent df9426d commit 742271f

File tree

2 files changed

+74
-32
lines changed

2 files changed

+74
-32
lines changed

src/ng/compile.js

+30-24
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,7 @@ function $CompileProvider($provide) {
536536
directives = collectDirectives(nodeList[i], [], attrs, i == 0 ? maxPriority : undefined, ignoreDirective);
537537

538538
nodeLinkFn = (directives.length)
539-
? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement)
539+
? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, null, [], [])
540540
: null;
541541

542542
childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || !nodeList[i].childNodes || !nodeList[i].childNodes.length)
@@ -747,12 +747,15 @@ function $CompileProvider($provide) {
747747
* scope argument is auto-generated to the new child of the transcluded parent scope.
748748
* @param {JQLite} jqCollection If we are working on the root of the compile tree then this
749749
* argument has the root jqLite array so that we can replace nodes on it.
750+
* @param {Object=} ignoreDirective An optional directive that will be ignored when compiling
751+
* the transclusion.
752+
* @param {Array.<Function>} preLinkFns
753+
* @param {Array.<Function>} postLinkFns
750754
* @returns linkFn
751755
*/
752-
function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, jqCollection, originalReplaceDirective) {
756+
function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, jqCollection,
757+
originalReplaceDirective, preLinkFns, postLinkFns) {
753758
var terminalPriority = -Number.MAX_VALUE,
754-
preLinkFns = [],
755-
postLinkFns = [],
756759
newScopeDirective = null,
757760
newIsolateScopeDirective = null,
758761
templateDirective = null,
@@ -784,18 +787,24 @@ function $CompileProvider($provide) {
784787
}
785788

786789
if (directiveValue = directive.scope) {
787-
assertNoDuplicate('isolated scope', newIsolateScopeDirective, directive, $compileNode);
788-
if (isObject(directiveValue)) {
789-
safeAddClass($compileNode, 'ng-isolate-scope');
790-
newIsolateScopeDirective = directive;
791-
}
792-
safeAddClass($compileNode, 'ng-scope');
793790
newScopeDirective = newScopeDirective || directive;
791+
792+
// skip the check for directives with async templates, we'll check the derived sync directive when
793+
// the template arrives
794+
if (!directive.templateUrl) {
795+
assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, $compileNode);
796+
if (isObject(directiveValue)) {
797+
safeAddClass($compileNode, 'ng-isolate-scope');
798+
newIsolateScopeDirective = directive;
799+
}
800+
safeAddClass($compileNode, 'ng-scope');
801+
}
794802
}
795803

796804
directiveName = directive.name;
797805

798-
if (directiveValue = directive.controller) {
806+
if (!directive.templateUrl && directive.controller) {
807+
directiveValue = directive.controller;
799808
controllerDirectives = controllerDirectives || {};
800809
assertNoDuplicate("'" + directiveName + "' controller",
801810
controllerDirectives[directiveName], directive, $compileNode);
@@ -874,8 +883,9 @@ function $CompileProvider($provide) {
874883
if (directive.replace) {
875884
replaceDirective = directive;
876885
}
877-
nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i),
878-
nodeLinkFn, $compileNode, templateAttrs, jqCollection, childTranscludeFn);
886+
887+
nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode,
888+
templateAttrs, jqCollection, childTranscludeFn, preLinkFns, postLinkFns);
879889
ii = directives.length;
880890
} else if (directive.compile) {
881891
try {
@@ -1170,16 +1180,16 @@ function $CompileProvider($provide) {
11701180
}
11711181

11721182

1173-
function compileTemplateUrl(directives, beforeTemplateNodeLinkFn, $compileNode, tAttrs,
1174-
$rootElement, childTranscludeFn) {
1183+
function compileTemplateUrl(directives, $compileNode, tAttrs,
1184+
$rootElement, childTranscludeFn, preLinkFns, postLinkFns) {
11751185
var linkQueue = [],
11761186
afterTemplateNodeLinkFn,
11771187
afterTemplateChildLinkFn,
11781188
beforeTemplateCompileNode = $compileNode[0],
11791189
origAsyncDirective = directives.shift(),
11801190
// The fact that we have to copy and patch the directive seems wrong!
11811191
derivedSyncDirective = extend({}, origAsyncDirective, {
1182-
controller: null, templateUrl: null, transclude: null, scope: null, replace: null
1192+
templateUrl: null, transclude: null, replace: null
11831193
}),
11841194
templateUrl = (isFunction(origAsyncDirective.templateUrl))
11851195
? origAsyncDirective.templateUrl($compileNode, tAttrs)
@@ -1213,7 +1223,8 @@ function $CompileProvider($provide) {
12131223

12141224
directives.unshift(derivedSyncDirective);
12151225

1216-
afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, childTranscludeFn, $compileNode, origAsyncDirective);
1226+
afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs,
1227+
childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns);
12171228
forEach($rootElement, function(node, i) {
12181229
if (node == compileNode) {
12191230
$rootElement[i] = $compileNode[0];
@@ -1235,10 +1246,7 @@ function $CompileProvider($provide) {
12351246
replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
12361247
}
12371248

1238-
afterTemplateNodeLinkFn(
1239-
beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, controller),
1240-
scope, linkNode, $rootElement, controller
1241-
);
1249+
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, controller);
12421250
}
12431251
linkQueue = null;
12441252
}).
@@ -1253,9 +1261,7 @@ function $CompileProvider($provide) {
12531261
linkQueue.push(rootElement);
12541262
linkQueue.push(controller);
12551263
} else {
1256-
afterTemplateNodeLinkFn(function() {
1257-
beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, controller);
1258-
}, scope, node, rootElement, controller);
1264+
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, controller);
12591265
}
12601266
};
12611267
}

test/ng/compileSpec.js

+44-8
Original file line numberDiff line numberDiff line change
@@ -1046,7 +1046,10 @@ describe('$compile', function() {
10461046
priority: priority,
10471047
compile: function() {
10481048
log(name + '-C');
1049-
return function() { log(name + '-L'); }
1049+
return {
1050+
pre: function() { log(name + '-PreL'); },
1051+
post: function() { log(name + '-PostL'); }
1052+
}
10501053
}
10511054
}, options || {}));
10521055
});
@@ -1075,7 +1078,8 @@ describe('$compile', function() {
10751078
$rootScope.$digest();
10761079
expect(log).toEqual(
10771080
'first-C; FLUSH; second-C; last-C; third-C; ' +
1078-
'third-L; first-L; second-L; last-L');
1081+
'first-PreL; second-PreL; last-PreL; third-PreL; ' +
1082+
'third-PostL; first-PostL; second-PostL; last-PostL');
10791083

10801084
var span = element.find('span');
10811085
expect(span.attr('first')).toEqual('');
@@ -1099,7 +1103,8 @@ describe('$compile', function() {
10991103
$rootScope.$digest();
11001104
expect(log).toEqual(
11011105
'iFirst-C; FLUSH; iSecond-C; iThird-C; iLast-C; ' +
1102-
'iFirst-L; iSecond-L; iThird-L; iLast-L');
1106+
'iFirst-PreL; iSecond-PreL; iThird-PreL; iLast-PreL; ' +
1107+
'iFirst-PostL; iSecond-PostL; iThird-PostL; iLast-PostL');
11031108

11041109
var div = element.find('div');
11051110
expect(div.attr('i-first')).toEqual('');
@@ -1124,7 +1129,8 @@ describe('$compile', function() {
11241129
$rootScope.$digest();
11251130
expect(log).toEqual(
11261131
'first-C; FLUSH; second-C; last-C; third-C; ' +
1127-
'third-L; first-L; second-L; last-L');
1132+
'first-PreL; second-PreL; last-PreL; third-PreL; ' +
1133+
'third-PostL; first-PostL; second-PostL; last-PostL');
11281134

11291135
var span = element.find('span');
11301136
expect(span.attr('first')).toEqual('');
@@ -1149,7 +1155,8 @@ describe('$compile', function() {
11491155
$rootScope.$digest();
11501156
expect(log).toEqual(
11511157
'iFirst-C; FLUSH; iSecond-C; iThird-C; iLast-C; ' +
1152-
'iFirst-L; iSecond-L; iThird-L; iLast-L');
1158+
'iFirst-PreL; iSecond-PreL; iThird-PreL; iLast-PreL; ' +
1159+
'iFirst-PostL; iSecond-PostL; iThird-PostL; iLast-PostL');
11531160

11541161
var div = element.find('div');
11551162
expect(div.attr('i-first')).toEqual('');
@@ -1264,6 +1271,35 @@ describe('$compile', function() {
12641271
expect(element.text()).toBe('boom!1|boom!2|');
12651272
});
12661273
});
1274+
1275+
1276+
it('should support templateUrl with replace', function() {
1277+
// a regression https://github.com/angular/angular.js/issues/3792
1278+
module(function($compileProvider) {
1279+
$compileProvider.directive('simple', function() {
1280+
return {
1281+
templateUrl: '/some.html',
1282+
replace: true
1283+
};
1284+
});
1285+
});
1286+
1287+
inject(function($templateCache, $rootScope, $compile) {
1288+
$templateCache.put('/some.html',
1289+
'<div ng-switch="i">' +
1290+
'<div ng-switch-when="1">i = 1</div>' +
1291+
'<div ng-switch-default>I dont know what `i` is.</div>' +
1292+
'</div>');
1293+
1294+
element = $compile('<div simple></div>')($rootScope);
1295+
1296+
$rootScope.$apply(function() {
1297+
$rootScope.i = 1;
1298+
});
1299+
1300+
expect(element.html()).toContain('i = 1');
1301+
});
1302+
});
12671303
});
12681304

12691305

@@ -1482,7 +1518,7 @@ describe('$compile', function() {
14821518
function($rootScope, $compile) {
14831519
expect(function(){
14841520
$compile('<div class="iscope-a; scope-b"></div>');
1485-
}).toThrowMinErr('$compile', 'multidir', 'Multiple directives [iscopeA, scopeB] asking for isolated scope on: ' +
1521+
}).toThrowMinErr('$compile', 'multidir', 'Multiple directives [iscopeA, scopeB] asking for new/isolated scope on: ' +
14861522
'<div class="iscope-a; scope-b ng-isolate-scope ng-scope">');
14871523
})
14881524
);
@@ -2824,7 +2860,7 @@ describe('$compile', function() {
28242860
});
28252861

28262862

2827-
it('should make the result of a transclusion available to the parent directive in pre- and post- linking phase (templateUrl)',
2863+
it('should make the result of a transclusion available to the parent directive in post-linking phase (templateUrl)',
28282864
function() {
28292865
// when compiling an async directive the transclusion is always processed before the directive
28302866
// this is different compared to sync directive. delaying the transclusion makes little sense.
@@ -2850,7 +2886,7 @@ describe('$compile', function() {
28502886

28512887
element = $compile('<div trans><span>unicorn!</span></div>')($rootScope);
28522888
$rootScope.$apply();
2853-
expect(log).toEqual('pre(unicorn!); post(unicorn!)');
2889+
expect(log).toEqual('pre(); post(unicorn!)');
28542890
});
28552891
});
28562892
});

0 commit comments

Comments
 (0)