From 1caf0b6bee5781589e20f7a27a8c60e8b1b784f5 Mon Sep 17 00:00:00 2001 From: Lucas Mirelmann Date: Sat, 3 Oct 2015 01:26:14 +0200 Subject: [PATCH] fix($parse): evaluate once simple expressions in interpolations For simple expressions without filters that have a stateless interceptor then handle the 2nd phase parse evaluation using `inputs`. TL;DR This fixes the issue that interpolated simple expressions were evaluated twice within one digest loop. Long version, things happen in the following order: * There was an overhaul on $interpolate, this overhaul changed $parse and incorporated the concept of an interceptor. * Optimization on $parse landed so expressions that have filters without parameters (or the parameters are constants) would be evaluated in 2 phases, first to evaluate the expression sans the filter evaluation and then with the filter evaluation. This also used interceptors [the second evaluation issue was added here] * More optimizations on $parse landed and now expressions could be evaluated in 2 phases. One to get all the possible values that could change (lets call this state), the state was checked by $watch to know if an expression changed. The second to continue the evaluation (as long as this state is provided). This, once again, used interceptors The last change, was supposed to fix the issue, but there was an assumption in the existing code that the code would always generate the 2 phases functions, but that is not true. If the expression is simple enough (just like the one in your case) then the 2-phase evaluations functions are not generated. In this case, if a stateless interceptor was added (just like what $interpolate adds) then the state was not used and you see the function being evaluated twice. This explains why, if you change the expression from `Hello {{log('A')}} {{log('B')}}!` to `Hello {{log('A') + ' ' + log('B')}}!`, then the repetition is not there. Closes #12983 Closes #13002 --- src/ng/parse.js | 4 +++- test/ng/compileSpec.js | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/ng/parse.js b/src/ng/parse.js index 7edc67560fde..a8ec5c8a3b8f 100644 --- a/src/ng/parse.js +++ b/src/ng/parse.js @@ -1906,13 +1906,14 @@ function $ParseProvider() { function addInterceptor(parsedExpression, interceptorFn) { if (!interceptorFn) return parsedExpression; var watchDelegate = parsedExpression.$$watchDelegate; + var useInputs = false; var regularWatch = watchDelegate !== oneTimeLiteralWatchDelegate && watchDelegate !== oneTimeWatchDelegate; var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) { - var value = parsedExpression(scope, locals, assign, inputs); + var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs); return interceptorFn(value, scope, locals); } : function oneTimeInterceptedExpression(scope, locals, assign, inputs) { var value = parsedExpression(scope, locals, assign, inputs); @@ -1930,6 +1931,7 @@ function $ParseProvider() { // If there is an interceptor, but no watchDelegate then treat the interceptor like // we treat filters - it is assumed to be a pure function unless flagged with $stateful fn.$$watchDelegate = inputsWatchDelegate; + useInputs = !parsedExpression.inputs; fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression]; } diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index 76cc889604c4..934cc1b0f69f 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -947,6 +947,14 @@ describe('$compile', function() { expect(child).toHaveClass('log'); // merged from replace directive template })); + it('should interpolate the values once per digest', + inject(function($compile, $rootScope, log) { + element = $compile('
{{log("A")}} foo {{::log("B")}}
')($rootScope); + $rootScope.log = log; + $rootScope.$digest(); + expect(log).toEqual('A; B; A; B'); + })); + it('should update references to replaced jQuery context', function() { module(function($compileProvider) { $compileProvider.directive('foo', function() {