aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIgor Minar2013-10-01 07:55:47 -0700
committerIgor Minar2013-10-03 22:23:37 -0700
commit31f190d4d53921d32253ba80d9ebe57d6c1de82b (patch)
tree147f87340b3ca3cd41d31537f876642c70bd133f
parentfe2145016cb057c92f9f01b32c58b4d7259eb6ee (diff)
downloadangular.js-31f190d4d53921d32253ba80d9ebe57d6c1de82b.tar.bz2
fix($compile): fix (reverse) directive postLink fn execution order
previously the compile/link fns executed in this order controlled via priority: - CompilePriorityHigh, CompilePriorityMedium, CompilePriorityLow - PreLinkPriorityHigh, PreLinkPriorityMedium, PreLinkPriorityLow - link children - PostLinkPriorityHigh, PostLinkPriorityMedium, PostLinkPriorityLow This was changed to: - CompilePriorityHigh, CompilePriorityMedium, CompilePriorityLow - PreLinkPriorityHigh, PreLinkPriorityMedium, PreLinkPriorityLow - link children - PostLinkPriorityLow, PostLinkPriorityMedium , PostLinkPriorityHigh Using this order the child transclusion directive that gets replaced onto the current element get executed correctly (see issue #3558), and more generally, the order of execution of post linking function makes more sense. The incorrect order was an oversight that has gone unnoticed for many suns and moons. (FYI: postLink functions are the default linking functions) BREAKING CHANGE: the order of postLink fn is now mirror opposite of the order in which corresponding preLinking and compile functions execute. Very few directives in practice rely on order of postLinking function (unlike on the order of compile functions), so in the rare case of this change affecting an existing directive, it might be necessary to convert it to a preLinking function or give it negative priority (look at the diff of this commit to see how an internal attribute interpolation directive was adjusted). Closes #3558
-rw-r--r--src/ng/compile.js5
-rw-r--r--src/ng/directive/input.js2
-rwxr-xr-xtest/ng/compileSpec.js146
3 files changed, 123 insertions, 30 deletions
diff --git a/src/ng/compile.js b/src/ng/compile.js
index a4e44f01..fce6f34e 100644
--- a/src/ng/compile.js
+++ b/src/ng/compile.js
@@ -1095,7 +1095,7 @@ function $CompileProvider($provide) {
childLinkFn && childLinkFn(scope, linkNode.childNodes, undefined, boundTranscludeFn);
// POSTLINKING
- for(i = 0, ii = postLinkFns.length; i < ii; i++) {
+ for(i = postLinkFns.length - 1; i >= 0; i--) {
try {
linkFn = postLinkFns[i];
linkFn(scope, $element, attrs,
@@ -1328,7 +1328,7 @@ function $CompileProvider($provide) {
}
directives.push({
- priority: 100,
+ priority: -100,
compile: valueFn(function attrInterpolateLinkFn(scope, element, attr) {
var $$observers = (attr.$$observers || (attr.$$observers = {}));
@@ -1346,6 +1346,7 @@ function $CompileProvider($provide) {
// register any observers
if (!interpolateFn) return;
+ // TODO(i): this should likely be attr.$set(name, iterpolateFn(scope) so that we reset the actual attr value
attr[name] = interpolateFn(scope);
($$observers[name] || ($$observers[name] = [])).$$inter = true;
(attr.$$observers && attr.$$observers[name].$$scope || scope).
diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js
index fa953419..ee613a6e 100644
--- a/src/ng/directive/input.js
+++ b/src/ng/directive/input.js
@@ -958,7 +958,7 @@ var VALID_CLASS = 'ng-valid',
</file>
* </example>
*
- *
+ *
*/
var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse',
function($scope, $exceptionHandler, $attr, $element, $parse) {
diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js
index f164ca7a..5e28c62b 100755
--- a/test/ng/compileSpec.js
+++ b/test/ng/compileSpec.js
@@ -96,19 +96,25 @@ describe('$compile', function() {
directive('div', function(log) {
return {
restrict: 'ECA',
- link: log.fn('1')
+ link: {
+ pre: log.fn('pre1'),
+ post: log.fn('post1')
+ }
};
});
directive('div', function(log) {
return {
restrict: 'ECA',
- link: log.fn('2')
+ link: {
+ pre: log.fn('pre2'),
+ post: log.fn('post2')
+ }
};
});
});
inject(function($compile, $rootScope, log) {
element = $compile('<div></div>')($rootScope);
- expect(log).toEqual('1; 2');
+ expect(log).toEqual('pre1; pre2; post2; post1');
});
});
});
@@ -206,7 +212,7 @@ describe('$compile', function() {
'<span greet="angular" log="L" x-high-log="H" data-medium-log="M"></span>')
($rootScope);
expect(element.text()).toEqual('Hello angular');
- expect(log).toEqual('H; M; L');
+ expect(log).toEqual('L; M; H');
}));
@@ -387,7 +393,7 @@ describe('$compile', function() {
element = $compile(
'<span log="L" x-high-log="H" data-medium-log="M"></span>')
($rootScope);
- expect(log).toEqual('H; M; L');
+ expect(log).toEqual('L; M; H');
}));
});
@@ -511,8 +517,7 @@ describe('$compile', function() {
($rootScope);
$rootScope.$digest();
expect(element.text()).toEqual('Replace!');
- // HIGH goes after MEDIUM since it executes as part of replaced template
- expect(log).toEqual('MEDIUM; HIGH; LOG');
+ expect(log).toEqual('LOG; HIGH; MEDIUM');
}));
@@ -521,7 +526,7 @@ describe('$compile', function() {
($rootScope);
$rootScope.$digest();
expect(element.text()).toEqual('Append!');
- expect(log).toEqual('HIGH; LOG; MEDIUM');
+ expect(log).toEqual('LOG; HIGH; MEDIUM');
}));
@@ -1079,7 +1084,7 @@ describe('$compile', function() {
expect(log).toEqual(
'first-C; FLUSH; second-C; last-C; third-C; ' +
'first-PreL; second-PreL; last-PreL; third-PreL; ' +
- 'third-PostL; first-PostL; second-PostL; last-PostL');
+ 'third-PostL; last-PostL; second-PostL; first-PostL');
var span = element.find('span');
expect(span.attr('first')).toEqual('');
@@ -1104,7 +1109,7 @@ describe('$compile', function() {
expect(log).toEqual(
'iFirst-C; FLUSH; iSecond-C; iThird-C; iLast-C; ' +
'iFirst-PreL; iSecond-PreL; iThird-PreL; iLast-PreL; ' +
- 'iFirst-PostL; iSecond-PostL; iThird-PostL; iLast-PostL');
+ 'iLast-PostL; iThird-PostL; iSecond-PostL; iFirst-PostL');
var div = element.find('div');
expect(div.attr('i-first')).toEqual('');
@@ -1130,7 +1135,7 @@ describe('$compile', function() {
expect(log).toEqual(
'first-C; FLUSH; second-C; last-C; third-C; ' +
'first-PreL; second-PreL; last-PreL; third-PreL; ' +
- 'third-PostL; first-PostL; second-PostL; last-PostL');
+ 'third-PostL; last-PostL; second-PostL; first-PostL');
var span = element.find('span');
expect(span.attr('first')).toEqual('');
@@ -1156,7 +1161,7 @@ describe('$compile', function() {
expect(log).toEqual(
'iFirst-C; FLUSH; iSecond-C; iThird-C; iLast-C; ' +
'iFirst-PreL; iSecond-PreL; iThird-PreL; iLast-PreL; ' +
- 'iFirst-PostL; iSecond-PostL; iThird-PostL; iLast-PostL');
+ 'iLast-PostL; iThird-PostL; iSecond-PostL; iFirst-PostL');
var div = element.find('div');
expect(div.attr('i-first')).toEqual('');
@@ -1344,10 +1349,10 @@ describe('$compile', function() {
scope: true,
restrict: 'CA',
compile: function() {
- return function (scope, element) {
+ return {pre: function (scope, element) {
log(scope.$id);
expect(element.data('$scope')).toBe(scope);
- };
+ }};
}
};
});
@@ -1409,9 +1414,9 @@ describe('$compile', function() {
directive('log', function(log) {
return {
restrict: 'CA',
- link: function(scope) {
+ link: {pre: function(scope) {
log('log-' + scope.$id + '-' + scope.$parent.$id);
- }
+ }}
};
});
}));
@@ -1419,7 +1424,7 @@ describe('$compile', function() {
it('should allow creation of new scopes', inject(function($rootScope, $compile, log) {
element = $compile('<div><span scope><a log></a></span></div>')($rootScope);
- expect(log).toEqual('LOG; log-002-001; 002');
+ expect(log).toEqual('002; log-002-001; LOG');
expect(element.find('span').hasClass('ng-scope')).toBe(true);
}));
@@ -1427,7 +1432,7 @@ describe('$compile', function() {
it('should allow creation of new isolated scopes for directives', inject(
function($rootScope, $compile, log) {
element = $compile('<div><span iscope><a log></a></span></div>')($rootScope);
- expect(log).toEqual('LOG; log-002-001; 002');
+ expect(log).toEqual('log-002-001; LOG; 002');
$rootScope.name = 'abc';
expect(iscope.$parent).toBe($rootScope);
expect(iscope.name).toBeUndefined();
@@ -1439,7 +1444,7 @@ describe('$compile', function() {
$httpBackend.expect('GET', 'tscope.html').respond('<a log>{{name}}; scopeId: {{$id}}</a>');
element = $compile('<div><span tscope></span></div>')($rootScope);
$httpBackend.flush();
- expect(log).toEqual('LOG; log-002-001; 002');
+ expect(log).toEqual('log-002-001; LOG; 002');
$rootScope.name = 'Jozo';
$rootScope.$apply();
expect(element.text()).toBe('Jozo; scopeId: 002');
@@ -1453,7 +1458,7 @@ describe('$compile', function() {
respond('<p><a log>{{name}}; scopeId: {{$id}}</a></p>');
element = $compile('<div><span trscope></span></div>')($rootScope);
$httpBackend.flush();
- expect(log).toEqual('LOG; log-002-001; 002');
+ expect(log).toEqual('log-002-001; LOG; 002');
$rootScope.name = 'Jozo';
$rootScope.$apply();
expect(element.text()).toBe('Jozo; scopeId: 002');
@@ -1467,7 +1472,7 @@ describe('$compile', function() {
respond('<p><a log>{{name}}; scopeId: {{$id}} |</a></p>');
element = $compile('<div><span ng-repeat="i in [1,2,3]" trscope></span></div>')($rootScope);
$httpBackend.flush();
- expect(log).toEqual('LOG; log-003-002; 003; LOG; log-005-004; 005; LOG; log-007-006; 007');
+ expect(log).toEqual('log-003-002; LOG; 003; log-005-004; LOG; 005; log-007-006; LOG; 007');
$rootScope.name = 'Jozo';
$rootScope.$apply();
expect(element.text()).toBe('Jozo; scopeId: 003 |Jozo; scopeId: 005 |Jozo; scopeId: 007 |');
@@ -1481,7 +1486,7 @@ describe('$compile', function() {
$httpBackend.expect('GET', 'tiscope.html').respond('<a log></a>');
element = $compile('<div><span tiscope></span></div>')($rootScope);
$httpBackend.flush();
- expect(log).toEqual('LOG; log-002-001; 002');
+ expect(log).toEqual('log-002-001; LOG; 002');
$rootScope.name = 'abc';
expect(iscope.$parent).toBe($rootScope);
expect(iscope.name).toBeUndefined();
@@ -1501,7 +1506,7 @@ describe('$compile', function() {
'</b>' +
'</div>'
)($rootScope);
- expect(log).toEqual('LOG; log-003-002; 003; LOG; log-002-001; 002; LOG; log-004-001; 004');
+ expect(log).toEqual('002; 003; log-003-002; LOG; log-002-001; LOG; 004; log-004-001; LOG');
})
);
@@ -1571,7 +1576,38 @@ describe('$compile', function() {
$rootScope.$digest();
expect(element.text()).toEqual('text: angular');
expect(element.attr('name')).toEqual('attr: angular');
- }));
+ })
+ );
+
+
+ it('should process attribute interpolation at the beginning of the post-linking phase', function() {
+ module(function() {
+ directive('attrLog', function(log) {
+ return {
+ compile: function($element, $attrs) {
+ log('compile=' + $attrs.myName);
+
+ return {
+ pre: function($scope, $element, $attrs) {
+ log('preLink=' + $attrs.myName);
+ },
+ post: function($scope, $element) {
+ log('postLink=' + $attrs.myName);
+ }
+ }
+ }
+ }
+ })
+ });
+ inject(function($rootScope, $compile, log) {
+ element = $compile('<div attr-log my-name="{{name}}"></div>')($rootScope);
+ $rootScope.name = 'angular';
+ $rootScope.$apply();
+ log('digest=' + element.attr('my-name'));
+ expect(log).toEqual('compile={{name}}; preLink={{name}}; postLink=; digest=angular');
+ });
+ });
+
describe('SCE values', function() {
it('should resolve compile and link both attribute and text bindings', inject(
@@ -1753,7 +1789,7 @@ describe('$compile', function() {
it('should compile from top to bottom but link from bottom up', inject(
function($compile, $rootScope, log) {
element = $compile('<a b><c></c></a>')($rootScope);
- expect(log).toEqual('tA; tB; tC; preA; preB; preC; postC; postA; postB');
+ expect(log).toEqual('tA; tB; tC; preA; preB; preC; postC; postB; postA');
}
));
@@ -2230,7 +2266,7 @@ describe('$compile', function() {
});
inject(function(log, $compile, $rootScope) {
element = $compile('<div main dep other></div>')($rootScope);
- expect(log).toEqual('main; dep:main; false');
+ expect(log).toEqual('false; dep:main; main');
});
});
@@ -2639,7 +2675,7 @@ describe('$compile', function() {
element = $compile('<div><div high-log trans="text" log>{{$parent.$id}}-{{$id}};</div></div>')
($rootScope);
$rootScope.$apply();
- expect(log).toEqual('compile: <!-- trans: text -->; HIGH; link; LOG; LOG');
+ expect(log).toEqual('compile: <!-- trans: text -->; link; LOG; LOG; HIGH');
expect(element.text()).toEqual('001-002;001-003;');
});
});
@@ -2907,6 +2943,62 @@ describe('$compile', function() {
});
+ it('should make the result of a transclusion available to the parent *replace* directive in post-linking phase (template)',
+ function() {
+ module(function() {
+ directive('replacedTrans', function(log) {
+ return {
+ transclude: true,
+ replace: true,
+ template: '<div ng-transclude></div>',
+ link: {
+ pre: function($scope, $element) {
+ log('pre(' + $element.text() + ')');
+ },
+ post: function($scope, $element) {
+ log('post(' + $element.text() + ')');
+ }
+ }
+ };
+ });
+ });
+ inject(function(log, $rootScope, $compile) {
+ element = $compile('<div replaced-trans><span>unicorn!</span></div>')($rootScope);
+ $rootScope.$apply();
+ expect(log).toEqual('pre(); post(unicorn!)');
+ });
+ });
+
+
+ it('should make the result of a transclusion available to the parent *replace* directive in post-linking phase (templateUrl)',
+ function() {
+ module(function() {
+ directive('replacedTrans', function(log) {
+ return {
+ transclude: true,
+ replace: true,
+ templateUrl: 'trans.html',
+ link: {
+ pre: function($scope, $element) {
+ log('pre(' + $element.text() + ')');
+ },
+ post: function($scope, $element) {
+ log('post(' + $element.text() + ')');
+ }
+ }
+ };
+ });
+ });
+ inject(function(log, $rootScope, $compile, $templateCache) {
+ $templateCache.put('trans.html', '<div ng-transclude></div>');
+
+ element = $compile('<div replaced-trans><span>unicorn!</span></div>')($rootScope);
+ $rootScope.$apply();
+ expect(log).toEqual('pre(); post(unicorn!)');
+ });
+ });
+
+
it('should terminate compilation only for element trasclusion', function() {
module(function() {
directive('elementTrans', function(log) {