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

fix+perf($parse): allow watching array/object literal values, disable deep watch for one-way bindings #15301

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/ng/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -3522,7 +3522,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
recordChanges(scopeName, newValue, oldValue);
destination[scopeName] = newValue;
}, deepWatch);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deep watching never happens, but we still have a variable named deepWatch. Isn't it a bit misleading?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK so let's change deepWatch to isLiteral.


removeWatchCollection.push(removeWatch);
break;
Expand Down
8 changes: 4 additions & 4 deletions src/ng/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -1786,13 +1786,13 @@ function $ParseProvider() {
}
}

function expressionInputDirtyCheck(newValue, oldValueOfValue) {
function expressionInputDirtyCheck(newValue, oldValueOfValue, compareObjectIdentity) {

if (newValue == null || oldValueOfValue == null) { // null/undefined
return newValue === oldValueOfValue;
}

if (typeof newValue === 'object') {
if (typeof newValue === 'object' && !compareObjectIdentity) {

// attempt to convert the value to a primitive type
// TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can
Expand Down Expand Up @@ -1821,7 +1821,7 @@ function $ParseProvider() {
inputExpressions = inputExpressions[0];
return scope.$watch(function expressionInputWatch(scope) {
var newInputValue = inputExpressions(scope);
if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf)) {
if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf, parsedExpression.literal)) {
lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]);
oldInputValueOf = newInputValue && getValueOf(newInputValue);
}
Expand All @@ -1841,7 +1841,7 @@ function $ParseProvider() {

for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
var newInputValue = inputExpressions[i](scope);
if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) {
if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i], parsedExpression.literal))) {
oldInputValues[i] = newInputValue;
oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue);
}
Expand Down
4 changes: 2 additions & 2 deletions test/ng/compileSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5676,7 +5676,7 @@ describe('$compile', function() {
}));


it('should deep-watch array literals', inject(function() {
it('should watch input values to array literals', inject(function() {
$rootScope.name = 'georgios';
$rootScope.obj = {name: 'pete'};
compile('<div><span my-component ow-ref="[{name: name}, obj]">');
Expand All @@ -5690,7 +5690,7 @@ describe('$compile', function() {
}));


it('should deep-watch object literals', inject(function() {
it('should watch input values object literals', inject(function() {
$rootScope.name = 'georgios';
$rootScope.obj = {name: 'pete'};
compile('<div><span my-component ow-ref="{name: name, item: obj}">');
Expand Down
68 changes: 68 additions & 0 deletions test/ng/parseSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3117,6 +3117,74 @@ describe('parser', function() {
scope.$digest();
expect(objB.value).toBe(scope.input);
}));

it('should support watching literals', inject(function($parse) {
var lastVal = NaN;
var callCount = 0;
var listener = function(val) { callCount++; lastVal = val; };

scope.$watch('{val: val}', listener);

scope.$apply('val = 1');
expect(callCount).toBe(1);
expect(lastVal).toEqual({val: 1});

scope.$apply('val = []');
expect(callCount).toBe(2);
expect(lastVal).toEqual({val: []});

scope.$apply('val = []');
expect(callCount).toBe(3);
expect(lastVal).toEqual({val: []});

scope.$apply('val = {}');
expect(callCount).toBe(4);
expect(lastVal).toEqual({val: {}});
}));

it('should only watch the direct inputs to literals', inject(function($parse) {
var lastVal = NaN;
var callCount = 0;
var listener = function(val) { callCount++; lastVal = val; };

scope.$watch('{val: val}', listener);

scope.$apply('val = 1');
expect(callCount).toBe(1);
expect(lastVal).toEqual({val: 1});

scope.$apply('val = [2]');
expect(callCount).toBe(2);
expect(lastVal).toEqual({val: [2]});

scope.$apply('val.push(3)');
expect(callCount).toBe(2);

scope.$apply('val.length = 0');
expect(callCount).toBe(2);
}));

it('should only watch the direct inputs to nested literals', inject(function($parse) {
var lastVal = NaN;
var callCount = 0;
var listener = function(val) { callCount++; lastVal = val; };

scope.$watch('[{val: [val]}]', listener);

scope.$apply('val = 1');
expect(callCount).toBe(1);
expect(lastVal).toEqual([{val: [1]}]);

scope.$apply('val = [2]');
expect(callCount).toBe(2);
expect(lastVal).toEqual([{val: [[2]]}]);

scope.$apply('val.push(3)');
expect(callCount).toBe(2);

scope.$apply('val.length = 0');
expect(callCount).toBe(2);
}));
});

describe('locals', function() {
Expand Down