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

Commit f1f892d

Browse files
fix($compile): don't run unnecessary update to one-way bindings
The watch handler was triggering on its first invocation, even though its change had already been dealt with when setting up the binding. Closes #14546 Closes #14580
1 parent 3b71474 commit f1f892d

File tree

2 files changed

+109
-11
lines changed

2 files changed

+109
-11
lines changed

src/ng/compile.js

+4-5
Original file line numberDiff line numberDiff line change
@@ -3213,14 +3213,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
32133213

32143214
parentGet = $parse(attrs[attrName]);
32153215

3216-
destination[scopeName] = parentGet(scope);
3216+
var initialValue = destination[scopeName] = parentGet(scope);
32173217
initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]);
32183218

32193219
removeWatch = scope.$watch(parentGet, function parentValueWatchAction(newValue, oldValue) {
3220-
if (newValue === oldValue) {
3221-
// If the new and old values are identical then this is the first time the watch has been triggered
3222-
// So instead we use the current value on the destination as the old value
3223-
oldValue = destination[scopeName];
3220+
if (oldValue === newValue) {
3221+
if (oldValue === initialValue) return;
3222+
oldValue = initialValue;
32243223
}
32253224
recordChanges(scopeName, newValue, oldValue);
32263225
destination[scopeName] = newValue;

test/ng/compileSpec.js

+105-6
Original file line numberDiff line numberDiff line change
@@ -3823,6 +3823,7 @@ describe('$compile', function() {
38233823

38243824
$rootScope.$apply('a = 7');
38253825
element = $compile('<c1 prop="a" attr="{{a}}"></c1>')($rootScope);
3826+
38263827
expect(log).toEqual([
38273828
{
38283829
prop: jasmine.objectContaining({currentValue: 7}),
@@ -3862,6 +3863,7 @@ describe('$compile', function() {
38623863
inject(function($compile, $rootScope) {
38633864
$rootScope.$apply('a = 7');
38643865
element = $compile('<c1 prop="a" attr="{{a}}"></c1>')($rootScope);
3866+
38653867
expect(log).toEqual([
38663868
{
38673869
prop: jasmine.objectContaining({currentValue: 7}),
@@ -4744,6 +4746,7 @@ describe('$compile', function() {
47444746
describe('one-way binding', function() {
47454747
it('should update isolate when the identity of origin changes', inject(function() {
47464748
compile('<div><span my-component ow-ref="obj">');
4749+
47474750
expect(componentScope.owRef).toBeUndefined();
47484751
expect(componentScope.owRefAlias).toBe(componentScope.owRef);
47494752

@@ -4780,6 +4783,7 @@ describe('$compile', function() {
47804783

47814784
it('should update isolate when both change', inject(function() {
47824785
compile('<div><span my-component ow-ref="name">');
4786+
47834787
$rootScope.name = {mark:123};
47844788
componentScope.owRef = 'misko';
47854789

@@ -4796,6 +4800,101 @@ describe('$compile', function() {
47964800
expect(componentScope.owRefAlias).toBe($rootScope.name);
47974801
}));
47984802

4803+
describe('initialization', function() {
4804+
var component, log;
4805+
4806+
beforeEach(function() {
4807+
log = [];
4808+
angular.module('owComponentTest', [])
4809+
.component('owComponent', {
4810+
bindings: { input: '<' },
4811+
controller: function() {
4812+
component = this;
4813+
this.input = 'constructor';
4814+
log.push('constructor');
4815+
4816+
this.$onInit = function() {
4817+
this.input = '$onInit';
4818+
log.push('$onInit');
4819+
};
4820+
4821+
this.$onChanges = function(changes) {
4822+
if (changes.input) {
4823+
log.push(['$onChanges', changes.input]);
4824+
}
4825+
};
4826+
}
4827+
});
4828+
});
4829+
4830+
it('should not update isolate again after $onInit if outer has not changed', function() {
4831+
module('owComponentTest');
4832+
inject(function() {
4833+
$rootScope.name = 'outer';
4834+
compile('<ow-component input="name"></ow-component>');
4835+
4836+
expect($rootScope.name).toEqual('outer');
4837+
expect(component.input).toEqual('$onInit');
4838+
4839+
$rootScope.$apply();
4840+
4841+
expect($rootScope.name).toEqual('outer');
4842+
expect(component.input).toEqual('$onInit');
4843+
4844+
expect(log).toEqual([
4845+
'constructor',
4846+
['$onChanges', jasmine.objectContaining({ currentValue: 'outer' })],
4847+
'$onInit'
4848+
]);
4849+
});
4850+
});
4851+
4852+
it('should update isolate again after $onInit if outer has changed (before initial watchAction call)', function() {
4853+
module('owComponentTest');
4854+
inject(function() {
4855+
$rootScope.name = 'outer1';
4856+
compile('<ow-component input="name"></ow-component>');
4857+
4858+
expect(component.input).toEqual('$onInit');
4859+
$rootScope.$apply('name = "outer2"');
4860+
4861+
expect($rootScope.name).toEqual('outer2');
4862+
expect(component.input).toEqual('outer2');
4863+
expect(log).toEqual([
4864+
'constructor',
4865+
['$onChanges', jasmine.objectContaining({ currentValue: 'outer1' })],
4866+
'$onInit',
4867+
['$onChanges', jasmine.objectContaining({ currentValue: 'outer2', previousValue: 'outer1' })]
4868+
]);
4869+
});
4870+
});
4871+
4872+
it('should update isolate again after $onInit if outer has changed (before initial watchAction call)', function() {
4873+
angular.module('owComponentTest')
4874+
.directive('changeInput', function() {
4875+
return function(scope, elem, attrs) {
4876+
scope.name = 'outer2';
4877+
};
4878+
});
4879+
module('owComponentTest');
4880+
inject(function() {
4881+
$rootScope.name = 'outer1';
4882+
compile('<ow-component input="name" change-input></ow-component>');
4883+
4884+
expect(component.input).toEqual('$onInit');
4885+
$rootScope.$digest();
4886+
4887+
expect($rootScope.name).toEqual('outer2');
4888+
expect(component.input).toEqual('outer2');
4889+
expect(log).toEqual([
4890+
'constructor',
4891+
['$onChanges', jasmine.objectContaining({ currentValue: 'outer1' })],
4892+
'$onInit',
4893+
['$onChanges', jasmine.objectContaining({ currentValue: 'outer2', previousValue: 'outer1' })]
4894+
]);
4895+
});
4896+
});
4897+
});
47994898

48004899
it('should not break when isolate and origin both change to the same value', inject(function() {
48014900
$rootScope.name = 'aaa';
@@ -4819,7 +4918,6 @@ describe('$compile', function() {
48194918
$rootScope.name = {mark:123};
48204919
compile('<div><span my-component ow-ref="name">');
48214920

4822-
$rootScope.$apply();
48234921
expect($rootScope.name).toEqual({mark:123});
48244922
expect(componentScope.owRef).toBe($rootScope.name);
48254923
expect(componentScope.owRefAlias).toBe($rootScope.name);
@@ -4836,7 +4934,6 @@ describe('$compile', function() {
48364934
$rootScope.obj = {mark:123};
48374935
compile('<div><span my-component ow-ref="obj">');
48384936

4839-
$rootScope.$apply();
48404937
expect($rootScope.obj).toEqual({mark:123});
48414938
expect(componentScope.owRef).toBe($rootScope.obj);
48424939

@@ -4849,6 +4946,7 @@ describe('$compile', function() {
48494946

48504947
it('should not throw on non assignable expressions in the parent', inject(function() {
48514948
compile('<div><span my-component ow-ref="\'hello \' + name">');
4949+
48524950
$rootScope.name = 'world';
48534951
$rootScope.$apply();
48544952
expect(componentScope.owRef).toBe('hello world');
@@ -4865,7 +4963,7 @@ describe('$compile', function() {
48654963

48664964
it('should not throw when assigning to undefined', inject(function() {
48674965
compile('<div><span my-component>');
4868-
$rootScope.$apply();
4966+
48694967
expect(componentScope.owRef).toBeUndefined();
48704968

48714969
componentScope.owRef = 'ignore me';
@@ -4879,6 +4977,7 @@ describe('$compile', function() {
48794977
it('should update isolate scope when "<"-bound NaN changes', inject(function() {
48804978
$rootScope.num = NaN;
48814979
compile('<div my-component ow-ref="num"></div>');
4980+
48824981
var isolateScope = element.isolateScope();
48834982
expect(isolateScope.owRef).toBeNaN();
48844983

@@ -4917,7 +5016,7 @@ describe('$compile', function() {
49175016
$rootScope.name = 'georgios';
49185017
$rootScope.obj = {name: 'pete'};
49195018
compile('<div><span my-component ow-ref="[{name: name}, obj]">');
4920-
$rootScope.$apply();
5019+
49215020
expect(componentScope.owRef).toEqual([{name: 'georgios'}, {name: 'pete'}]);
49225021

49235022
$rootScope.name = 'lucas';
@@ -4931,7 +5030,7 @@ describe('$compile', function() {
49315030
$rootScope.name = 'georgios';
49325031
$rootScope.obj = {name: 'pete'};
49335032
compile('<div><span my-component ow-ref="{name: name, item: obj}">');
4934-
$rootScope.$apply();
5033+
49355034
expect(componentScope.owRef).toEqual({name: 'georgios', item: {name: 'pete'}});
49365035

49375036
$rootScope.name = 'lucas';
@@ -4967,7 +5066,6 @@ describe('$compile', function() {
49675066
function test(literalString, literalValue) {
49685067
compile('<div><span my-component ow-ref="' + literalString + '">');
49695068

4970-
$rootScope.$apply();
49715069
expect(componentScope.owRef).toBe(literalValue);
49725070
dealoc(element);
49735071
}
@@ -4976,6 +5074,7 @@ describe('$compile', function() {
49765074
describe('optional one-way binding', function() {
49775075
it('should update local when origin changes', inject(function() {
49785076
compile('<div><span my-component ow-optref="name">');
5077+
49795078
expect(componentScope.owOptref).toBeUndefined();
49805079
expect(componentScope.owOptrefAlias).toBe(componentScope.owOptref);
49815080

0 commit comments

Comments
 (0)