diff --git a/src/ng/compile.js b/src/ng/compile.js index 0265beb47250..03b7237bdb21 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -1803,9 +1803,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { bindings = parent.data('$binding') || []; bindings.push(interpolateFn); safeAddClass(parent.data('$binding', bindings), 'ng-binding'); - scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { + scope.$watchSet(interpolateFn.expressions, interpolateFn.$$invoke(function(value) { node[0].nodeValue = value; - }); + })); }) }); } @@ -1866,7 +1866,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { attr[name] = interpolateFn(scope); ($$observers[name] || ($$observers[name] = [])).$$inter = true; (attr.$$observers && attr.$$observers[name].$$scope || scope). - $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { + $watchSet(interpolateFn.expressions, interpolateFn.$$invoke(function (newValue, oldValue) { //special case for class attribute addition + removal //so that class changes can tap into the animation //hooks provided by the $animate service. Be sure to @@ -1878,7 +1878,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } else { attr.$set(name, newValue); } - }); + })); } }; } diff --git a/src/ng/interpolate.js b/src/ng/interpolate.js index 6b61e56f2a70..731f32590b10 100644 --- a/src/ng/interpolate.js +++ b/src/ng/interpolate.js @@ -125,32 +125,33 @@ function $InterpolateProvider() { var startIndex, endIndex, index = 0, - parts = [], - length = text.length, + separators = [], + expressions = [], hasInterpolation = false, + hasText = false, fn, exp, concat = []; - while(index < length) { + while(index < text.length) { if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) && ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) { - (index != startIndex) && parts.push(text.substring(index, startIndex)); - parts.push(fn = $parse(exp = text.substring(startIndex + startSymbolLength, endIndex))); + if (index !== startIndex) hasText = true; + separators.push(text.substring(index, startIndex)); + expressions.push(fn = interpolateParse(exp = text.substring(startIndex + startSymbolLength, endIndex))); fn.exp = exp; index = endIndex + endSymbolLength; hasInterpolation = true; } else { - // we did not find anything, so we have to add the remainder to the parts array - (index != length) && parts.push(text.substring(index)); - index = length; + // we did not find an interpolation, so we have to add the remainder to the separators array + if (index !== text.length) hasText = true; + separators.push(text.substring(index)); + break; } } - if (!(length = parts.length)) { - // we added, nothing, must have been an empty string. - parts.push(''); - length = 1; + if (separators.length === expressions.length) { + separators.push(''); } // Concatenating expressions makes it hard to reason about whether some combination of @@ -159,7 +160,7 @@ function $InterpolateProvider() { // that's used is assigned or constructed by some JS code somewhere that is more testable or // make it obvious that you bound the value to some user controlled value. This helps reduce // the load when auditing for XSS issues. - if (trustedContext && parts.length > 1) { + if (trustedContext && hasInterpolation && (hasText || expressions.length > 1)) { throw $interpolateMinErr('noconcat', "Error while interpolating: {0}\nStrict Contextual Escaping disallows " + "interpolations that concatenate multiple expressions when a trusted value is " + @@ -167,36 +168,54 @@ function $InterpolateProvider() { } if (!mustHaveExpression || hasInterpolation) { - concat.length = length; + concat.length = separators.length + expressions.length; + var computeFn = function (values, context) { + for(var i = 0, ii = expressions.length; i} watchExpressions Array of expressions that will be individually + * watched using {@link ng.$rootScope.Scope#$watch $watch()} + * + * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any + * expression in `watchExpressions` changes + * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching + * those of `watchExpression` + * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching + * those of `watchExpression` + * The `scope` refers to the current scope. + * + * @returns {function()} Returns a de-registration function for all listeners. + */ + $watchSet: function (watchExpressions, listener) { + if (watchExpressions.length === 0) return noop; + + var self = this, + oldValues = new Array(watchExpressions.length), + newValues = new Array(watchExpressions.length); + + var deregisterFns = [], + changeCount = 0; + + forEach(watchExpressions, function (expr, i) { + deregisterFns.push(self.$watch(expr, function (value, oldValue) { + newValues[i] = value; + oldValues[i] = oldValue; + changeCount++; + })); + }); + + deregisterFns.push(this.$watch(function () {return changeCount;}, function () { + listener.call(this, newValues, oldValues, self); + })); + + return function () { + forEach(deregisterFns, function (fn) { + fn(); + }); + }; + }, + /** * @ngdoc method diff --git a/src/ngScenario/Scenario.js b/src/ngScenario/Scenario.js index f323e01d0f5a..a8e6e9d1f591 100644 --- a/src/ngScenario/Scenario.js +++ b/src/ngScenario/Scenario.js @@ -314,8 +314,8 @@ _jQuery.fn.bindings = function(windowJquery, bindExp) { } for(var fns, j=0, jj=binding.length; j