Skip to content

Commit d4289a6

Browse files
jbedardellimist
authored andcommitted
perf($compile): do not use deepWatch in literal one-way bindings
Avoiding deep watchers for array/object literals will improve watcher performance of all literals passed as one-way bindings, especially those containing references to large/complex objects. BREAKING CHANGE: Previously when a literal value was passed into a directive/component via one-way binding it would be watched with a deep watcher. For example, for `<my-component input="[a]">`, a new instance of the array would be passed into the directive/component (and trigger $onChanges) not only if `a` changed but also if any sub property of `a` changed such as `a.b` or `a.b.c.d.e` etc. This also means a new but equal value for `a` would NOT trigger such a change. Now literal values use an input-based watch similar to other directive/component one-way bindings. In this context inputs are the non-constant parts of the literal. In the example above the input would be `a`. Changes are only triggered when the inputs to the literal change. Closes angular#15301
1 parent b38dc49 commit d4289a6

File tree

2 files changed

+77
-5
lines changed

2 files changed

+77
-5
lines changed

src/ng/compile.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -3523,21 +3523,21 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
35233523
if (optional && !attrs[attrName]) break;
35243524

35253525
parentGet = $parse(attrs[attrName]);
3526-
var deepWatch = parentGet.literal;
3526+
var isLiteral = parentGet.literal;
35273527

35283528
var initialValue = destination[scopeName] = parentGet(scope);
35293529
initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]);
35303530

35313531
removeWatch = scope.$watch(parentGet, function parentValueWatchAction(newValue, oldValue) {
35323532
if (oldValue === newValue) {
3533-
if (oldValue === initialValue || (deepWatch && equals(oldValue, initialValue))) {
3533+
if (oldValue === initialValue || (isLiteral && equals(oldValue, initialValue))) {
35343534
return;
35353535
}
35363536
oldValue = initialValue;
35373537
}
35383538
recordChanges(scopeName, newValue, oldValue);
35393539
destination[scopeName] = newValue;
3540-
}, deepWatch);
3540+
});
35413541

35423542
removeWatchCollection.push(removeWatch);
35433543
break;

test/ng/compileSpec.js

+74-2
Original file line numberDiff line numberDiff line change
@@ -4306,6 +4306,78 @@ describe('$compile', function() {
43064306
});
43074307

43084308

4309+
it('should trigger `$onChanges` for literal expressions when expression input value changes (simple value)', function() {
4310+
var log = [];
4311+
function TestController() { }
4312+
TestController.prototype.$onChanges = function(change) { log.push(change); };
4313+
4314+
angular.module('my', [])
4315+
.component('c1', {
4316+
controller: TestController,
4317+
bindings: { 'prop1': '<' }
4318+
});
4319+
4320+
module('my');
4321+
inject(function($compile, $rootScope) {
4322+
element = $compile('<c1 prop1="[val]"></c1>')($rootScope);
4323+
4324+
$rootScope.$apply('val = 1');
4325+
expect(log.pop()).toEqual({prop1: jasmine.objectContaining({previousValue: [undefined], currentValue: [1]})});
4326+
4327+
$rootScope.$apply('val = 2');
4328+
expect(log.pop()).toEqual({prop1: jasmine.objectContaining({previousValue: [1], currentValue: [2]})});
4329+
});
4330+
});
4331+
4332+
4333+
it('should trigger `$onChanges` for literal expressions when expression input value changes (complex value)', function() {
4334+
var log = [];
4335+
function TestController() { }
4336+
TestController.prototype.$onChanges = function(change) { log.push(change); };
4337+
4338+
angular.module('my', [])
4339+
.component('c1', {
4340+
controller: TestController,
4341+
bindings: { 'prop1': '<' }
4342+
});
4343+
4344+
module('my');
4345+
inject(function($compile, $rootScope) {
4346+
element = $compile('<c1 prop1="[val]"></c1>')($rootScope);
4347+
4348+
$rootScope.$apply('val = [1]');
4349+
expect(log.pop()).toEqual({prop1: jasmine.objectContaining({previousValue: [undefined], currentValue: [[1]]})});
4350+
4351+
$rootScope.$apply('val = [2]');
4352+
expect(log.pop()).toEqual({prop1: jasmine.objectContaining({previousValue: [[1]], currentValue: [[2]]})});
4353+
});
4354+
});
4355+
4356+
4357+
it('should trigger `$onChanges` for literal expressions when expression input value changes instances, even when equal', function() {
4358+
var log = [];
4359+
function TestController() { }
4360+
TestController.prototype.$onChanges = function(change) { log.push(change); };
4361+
4362+
angular.module('my', [])
4363+
.component('c1', {
4364+
controller: TestController,
4365+
bindings: { 'prop1': '<' }
4366+
});
4367+
4368+
module('my');
4369+
inject(function($compile, $rootScope) {
4370+
element = $compile('<c1 prop1="[val]"></c1>')($rootScope);
4371+
4372+
$rootScope.$apply('val = [1]');
4373+
expect(log.pop()).toEqual({prop1: jasmine.objectContaining({previousValue: [undefined], currentValue: [[1]]})});
4374+
4375+
$rootScope.$apply('val = [1]');
4376+
expect(log.pop()).toEqual({prop1: jasmine.objectContaining({previousValue: [[1]], currentValue: [[1]]})});
4377+
});
4378+
});
4379+
4380+
43094381
it('should pass the original value as `previousValue` even if there were multiple changes in a single digest', function() {
43104382
var log = [];
43114383
function TestController() { }
@@ -5832,7 +5904,7 @@ describe('$compile', function() {
58325904
}));
58335905

58345906

5835-
it('should deep-watch array literals', inject(function() {
5907+
it('should watch input values to array literals', inject(function() {
58365908
$rootScope.name = 'georgios';
58375909
$rootScope.obj = {name: 'pete'};
58385910
compile('<div><span my-component ow-ref="[{name: name}, obj]">');
@@ -5846,7 +5918,7 @@ describe('$compile', function() {
58465918
}));
58475919

58485920

5849-
it('should deep-watch object literals', inject(function() {
5921+
it('should watch input values object literals', inject(function() {
58505922
$rootScope.name = 'georgios';
58515923
$rootScope.obj = {name: 'pete'};
58525924
compile('<div><span my-component ow-ref="{name: name, item: obj}">');

0 commit comments

Comments
 (0)