Skip to content

Commit 15de372

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 angular#14546
1 parent 824ce30 commit 15de372

File tree

2 files changed

+104
-11
lines changed

2 files changed

+104
-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

+100-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,96 @@ 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+
expect(log).toEqual([
4839+
'constructor',
4840+
['$onChanges', jasmine.objectContaining({ currentValue: 'outer' })],
4841+
'$onInit'
4842+
]);
4843+
});
4844+
});
4845+
4846+
4847+
it('should update isolate again after $onInit if outer has changed (before initial watchAction call)', function() {
4848+
module('owComponentTest');
4849+
inject(function() {
4850+
$rootScope.name = 'outer1';
4851+
compile('<ow-component input="name"></ow-component>');
4852+
4853+
expect(component.input).toEqual('$onInit');
4854+
$rootScope.$apply('name = "outer2"');
4855+
4856+
expect($rootScope.name).toEqual('outer2');
4857+
expect(component.input).toEqual('outer2');
4858+
expect(log).toEqual([
4859+
'constructor',
4860+
['$onChanges', jasmine.objectContaining({ currentValue: 'outer1' })],
4861+
'$onInit',
4862+
['$onChanges', jasmine.objectContaining({ currentValue: 'outer2', previousValue: 'outer1' })]
4863+
]);
4864+
});
4865+
});
4866+
4867+
it('should update isolate again after $onInit if outer has changed (before initial watchAction call)', function() {
4868+
angular.module('owComponentTest')
4869+
.directive('changeInput', function() {
4870+
return function(scope, elem, attrs) {
4871+
scope.name = 'outer2';
4872+
};
4873+
});
4874+
module('owComponentTest');
4875+
inject(function() {
4876+
$rootScope.name = 'outer1';
4877+
compile('<ow-component input="name" change-input></ow-component>');
4878+
4879+
expect(component.input).toEqual('$onInit');
4880+
$rootScope.$digest();
4881+
4882+
expect($rootScope.name).toEqual('outer2');
4883+
expect(component.input).toEqual('outer2');
4884+
expect(log).toEqual([
4885+
'constructor',
4886+
['$onChanges', jasmine.objectContaining({ currentValue: 'outer1' })],
4887+
'$onInit',
4888+
['$onChanges', jasmine.objectContaining({ currentValue: 'outer2', previousValue: 'outer1' })]
4889+
]);
4890+
});
4891+
});
4892+
});
47994893

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

4822-
$rootScope.$apply();
48234916
expect($rootScope.name).toEqual({mark:123});
48244917
expect(componentScope.owRef).toBe($rootScope.name);
48254918
expect(componentScope.owRefAlias).toBe($rootScope.name);
@@ -4836,7 +4929,6 @@ describe('$compile', function() {
48364929
$rootScope.obj = {mark:123};
48374930
compile('<div><span my-component ow-ref="obj">');
48384931

4839-
$rootScope.$apply();
48404932
expect($rootScope.obj).toEqual({mark:123});
48414933
expect(componentScope.owRef).toBe($rootScope.obj);
48424934

@@ -4849,6 +4941,7 @@ describe('$compile', function() {
48494941

48504942
it('should not throw on non assignable expressions in the parent', inject(function() {
48514943
compile('<div><span my-component ow-ref="\'hello \' + name">');
4944+
48524945
$rootScope.name = 'world';
48534946
$rootScope.$apply();
48544947
expect(componentScope.owRef).toBe('hello world');
@@ -4865,7 +4958,7 @@ describe('$compile', function() {
48654958

48664959
it('should not throw when assigning to undefined', inject(function() {
48674960
compile('<div><span my-component>');
4868-
$rootScope.$apply();
4961+
48694962
expect(componentScope.owRef).toBeUndefined();
48704963

48714964
componentScope.owRef = 'ignore me';
@@ -4879,6 +4972,7 @@ describe('$compile', function() {
48794972
it('should update isolate scope when "<"-bound NaN changes', inject(function() {
48804973
$rootScope.num = NaN;
48814974
compile('<div my-component ow-ref="num"></div>');
4975+
48824976
var isolateScope = element.isolateScope();
48834977
expect(isolateScope.owRef).toBeNaN();
48844978

@@ -4917,7 +5011,7 @@ describe('$compile', function() {
49175011
$rootScope.name = 'georgios';
49185012
$rootScope.obj = {name: 'pete'};
49195013
compile('<div><span my-component ow-ref="[{name: name}, obj]">');
4920-
$rootScope.$apply();
5014+
49215015
expect(componentScope.owRef).toEqual([{name: 'georgios'}, {name: 'pete'}]);
49225016

49235017
$rootScope.name = 'lucas';
@@ -4931,7 +5025,7 @@ describe('$compile', function() {
49315025
$rootScope.name = 'georgios';
49325026
$rootScope.obj = {name: 'pete'};
49335027
compile('<div><span my-component ow-ref="{name: name, item: obj}">');
4934-
$rootScope.$apply();
5028+
49355029
expect(componentScope.owRef).toEqual({name: 'georgios', item: {name: 'pete'}});
49365030

49375031
$rootScope.name = 'lucas';
@@ -4967,7 +5061,6 @@ describe('$compile', function() {
49675061
function test(literalString, literalValue) {
49685062
compile('<div><span my-component ow-ref="' + literalString + '">');
49695063

4970-
$rootScope.$apply();
49715064
expect(componentScope.owRef).toBe(literalValue);
49725065
dealoc(element);
49735066
}
@@ -4976,6 +5069,7 @@ describe('$compile', function() {
49765069
describe('optional one-way binding', function() {
49775070
it('should update local when origin changes', inject(function() {
49785071
compile('<div><span my-component ow-optref="name">');
5072+
49795073
expect(componentScope.owOptref).toBeUndefined();
49805074
expect(componentScope.owOptrefAlias).toBe(componentScope.owOptref);
49815075

0 commit comments

Comments
 (0)