Skip to content

Commit f5ddb10

Browse files
jbedardNarretz
authored andcommitted
fix($parse): fix infinite digest errors when watching objects with .valueOf in literals
Closes angular#15867
1 parent 6a448d3 commit f5ddb10

File tree

2 files changed

+93
-2
lines changed

2 files changed

+93
-2
lines changed

src/ng/parse.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1788,14 +1788,14 @@ function $ParseProvider() {
17881788
return newValue === oldValueOfValue;
17891789
}
17901790

1791-
if (typeof newValue === 'object' && !compareObjectIdentity) {
1791+
if (typeof newValue === 'object') {
17921792

17931793
// attempt to convert the value to a primitive type
17941794
// TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can
17951795
// be cheaply dirty-checked
17961796
newValue = getValueOf(newValue);
17971797

1798-
if (typeof newValue === 'object') {
1798+
if (typeof newValue === 'object' && !compareObjectIdentity) {
17991799
// objects/arrays are not supported - deep-watching them would be too expensive
18001800
return false;
18011801
}

test/ng/parseSpec.js

+91
Original file line numberDiff line numberDiff line change
@@ -2872,6 +2872,40 @@ describe('parser', function() {
28722872
expect(called).toBe(true);
28732873
}));
28742874

2875+
it('should not invoke interceptorFns unless the input.valueOf changes even if the instance changes', inject(function($parse) {
2876+
var called = false;
2877+
function interceptor(v) {
2878+
called = true;
2879+
return v;
2880+
}
2881+
scope.$watch($parse('a', interceptor));
2882+
scope.a = new Date();
2883+
scope.$digest();
2884+
expect(called).toBe(true);
2885+
2886+
called = false;
2887+
scope.a = new Date(scope.a.valueOf());
2888+
scope.$digest();
2889+
expect(called).toBe(false);
2890+
}));
2891+
2892+
it('should invoke interceptorFns if input.valueOf changes even if the instance does not', inject(function($parse) {
2893+
var called = false;
2894+
function interceptor(v) {
2895+
called = true;
2896+
return v;
2897+
}
2898+
scope.$watch($parse('a', interceptor));
2899+
scope.a = new Date();
2900+
scope.$digest();
2901+
expect(called).toBe(true);
2902+
2903+
called = false;
2904+
scope.a.setTime(scope.a.getTime() + 1);
2905+
scope.$digest();
2906+
expect(called).toBe(true);
2907+
}));
2908+
28752909
it('should invoke interceptors when the expression is `undefined`', inject(function($parse) {
28762910
var called = false;
28772911
function interceptor(v) {
@@ -3040,6 +3074,63 @@ describe('parser', function() {
30403074
expect(called).toBe(true);
30413075
}));
30423076

3077+
it('should not reevaluate literals with non-primitive input that does support valueOf()',
3078+
inject(function($parse) {
3079+
3080+
var date = scope.date = new Date();
3081+
3082+
var parsed = $parse('[date]');
3083+
var watcherCalls = 0;
3084+
scope.$watch(parsed, function(input) {
3085+
expect(input[0]).toBe(date);
3086+
watcherCalls++;
3087+
});
3088+
3089+
scope.$digest();
3090+
expect(watcherCalls).toBe(1);
3091+
3092+
scope.$digest();
3093+
expect(watcherCalls).toBe(1);
3094+
}));
3095+
3096+
it('should not reevaluate literals with non-primitive input that does support valueOf()' +
3097+
' when the instance changes but valueOf() does not', inject(function($parse) {
3098+
3099+
scope.date = new Date(1234567890123);
3100+
3101+
var parsed = $parse('[date]');
3102+
var watcherCalls = 0;
3103+
scope.$watch(parsed, function(input) {
3104+
watcherCalls++;
3105+
});
3106+
3107+
scope.$digest();
3108+
expect(watcherCalls).toBe(1);
3109+
3110+
scope.date = new Date(1234567890123);
3111+
scope.$digest();
3112+
expect(watcherCalls).toBe(1);
3113+
}));
3114+
3115+
it('should reevaluate literals with non-primitive input that does support valueOf()' +
3116+
' when the instance does not change but valueOf() does', inject(function($parse) {
3117+
3118+
scope.date = new Date(1234567890123);
3119+
3120+
var parsed = $parse('[date]');
3121+
var watcherCalls = 0;
3122+
scope.$watch(parsed, function(input) {
3123+
watcherCalls++;
3124+
});
3125+
3126+
scope.$digest();
3127+
expect(watcherCalls).toBe(1);
3128+
3129+
scope.date.setTime(scope.date.getTime() + 1);
3130+
scope.$digest();
3131+
expect(watcherCalls).toBe(2);
3132+
}));
3133+
30433134
it('should continue with the evaluation of the expression without invoking computed parts',
30443135
inject(function($parse) {
30453136
var value = 'foo';

0 commit comments

Comments
 (0)