Skip to content

Commit 676106f

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 676106f

File tree

2 files changed

+55
-5
lines changed

2 files changed

+55
-5
lines changed

src/ng/compile.js

+1-5
Original file line numberDiff line numberDiff line change
@@ -3217,11 +3217,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
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];
3224-
}
3220+
if (oldValue === newValue) return;
32253221
recordChanges(scopeName, newValue, oldValue);
32263222
destination[scopeName] = newValue;
32273223
}, parentGet.literal);

test/ng/compileSpec.js

+54
Original file line numberDiff line numberDiff line change
@@ -3686,12 +3686,16 @@ describe('$compile', function() {
36863686
$rootScope.$watch('val', function(val, oldVal) { $rootScope.val2 = val * 2; });
36873687
// Setup the directive with two bindings
36883688
element = $compile('<c1 prop1="val" prop2="val2" other="val3" attr="{{val4}}"></c1>')($rootScope);
3689+
$rootScope.$apply();
36893690

36903691
expect(log).toEqual([
36913692
{
36923693
prop1: jasmine.objectContaining({currentValue: undefined}),
36933694
prop2: jasmine.objectContaining({currentValue: undefined}),
36943695
attr: jasmine.objectContaining({currentValue: ''})
3696+
},
3697+
{
3698+
prop2: jasmine.objectContaining({currentValue: NaN})
36953699
}
36963700
]);
36973701

@@ -3756,6 +3760,7 @@ describe('$compile', function() {
37563760
module('my');
37573761
inject(function($compile, $rootScope) {
37583762
element = $compile('<c1 prop1="val"></c1>')($rootScope);
3763+
$rootScope.$apply();
37593764

37603765
$rootScope.$apply('val = 1');
37613766
expect(log.pop()).toEqual({prop1: jasmine.objectContaining({previousValue: undefined, currentValue: 1})});
@@ -3823,6 +3828,8 @@ describe('$compile', function() {
38233828

38243829
$rootScope.$apply('a = 7');
38253830
element = $compile('<c1 prop="a" attr="{{a}}"></c1>')($rootScope);
3831+
$rootScope.$apply();
3832+
38263833
expect(log).toEqual([
38273834
{
38283835
prop: jasmine.objectContaining({currentValue: 7}),
@@ -3862,6 +3869,8 @@ describe('$compile', function() {
38623869
inject(function($compile, $rootScope) {
38633870
$rootScope.$apply('a = 7');
38643871
element = $compile('<c1 prop="a" attr="{{a}}"></c1>')($rootScope);
3872+
$rootScope.$apply();
3873+
38653874
expect(log).toEqual([
38663875
{
38673876
prop: jasmine.objectContaining({currentValue: 7}),
@@ -3911,8 +3920,10 @@ describe('$compile', function() {
39113920

39123921
// Setup two sibling components with bindings that will change
39133922
element = $compile('<div><c1 prop="val1"></c1><c2 prop="val2"></c2></div>')($rootScope);
3923+
$rootScope.$apply();
39143924

39153925
// Clear out initial changes
3926+
watchCount = 0;
39163927
log = [];
39173928

39183929
// Update val to trigger the onChanges
@@ -3958,6 +3969,7 @@ describe('$compile', function() {
39583969

39593970
// Setup the directive with two bindings
39603971
element = $compile('<outer prop1="a"></outer>')($rootScope);
3972+
$rootScope.$apply();
39613973

39623974
// Clear out initial changes
39633975
log = [];
@@ -3990,6 +4002,7 @@ describe('$compile', function() {
39904002

39914003
// Setup the directive with bindings that will keep updating the bound value forever
39924004
element = $compile('<c1 prop="a" on-change="a = -a"></c1>')($rootScope);
4005+
$rootScope.$apply();
39934006

39944007
// Update val to trigger the unstable onChanges, which will result in an error
39954008
expect(function() {
@@ -4025,6 +4038,7 @@ describe('$compile', function() {
40254038

40264039
// Setup the directive with bindings that will keep updating the bound value forever
40274040
element = $compile('<c1 prop="a" on-change="a = -a"></c1>')($rootScope);
4041+
$rootScope.$apply();
40284042

40294043
// Update val to trigger the unstable onChanges, which will result in an error
40304044
$rootScope.$apply('a = 42');
@@ -4744,6 +4758,8 @@ describe('$compile', function() {
47444758
describe('one-way binding', function() {
47454759
it('should update isolate when the identity of origin changes', inject(function() {
47464760
compile('<div><span my-component ow-ref="obj">');
4761+
$rootScope.$apply();
4762+
47474763
expect(componentScope.owRef).toBeUndefined();
47484764
expect(componentScope.owRefAlias).toBe(componentScope.owRef);
47494765

@@ -4780,6 +4796,8 @@ describe('$compile', function() {
47804796

47814797
it('should update isolate when both change', inject(function() {
47824798
compile('<div><span my-component ow-ref="name">');
4799+
$rootScope.$apply();
4800+
47834801
$rootScope.name = {mark:123};
47844802
componentScope.owRef = 'misko';
47854803

@@ -4796,6 +4814,35 @@ describe('$compile', function() {
47964814
expect(componentScope.owRefAlias).toBe($rootScope.name);
47974815
}));
47984816

4817+
it('should not update isolate again after $onInit if outer has not changed', function() {
4818+
var log = [];
4819+
var component;
4820+
angular.module('owComponentTest', [])
4821+
.component('owComponent', {
4822+
bindings: { input: '<' },
4823+
controller: function() {
4824+
component = this;
4825+
this.input = 'constructor';
4826+
this.$onInit = function() {
4827+
this.input = '$onInit';
4828+
};
4829+
this.$onChanges = function(changes) {
4830+
if (changes.input) {
4831+
log.push(changes.input);
4832+
}
4833+
};
4834+
}
4835+
});
4836+
module('owComponentTest');
4837+
inject(function() {
4838+
$rootScope.name = 'outer';
4839+
compile('<ow-component input="name"></ow-component>');
4840+
$rootScope.$apply();
4841+
4842+
expect($rootScope.name).toEqual('outer');
4843+
expect(component.input).toEqual('$onInit');
4844+
})
4845+
});
47994846

48004847
it('should not break when isolate and origin both change to the same value', inject(function() {
48014848
$rootScope.name = 'aaa';
@@ -4849,6 +4896,8 @@ describe('$compile', function() {
48494896

48504897
it('should not throw on non assignable expressions in the parent', inject(function() {
48514898
compile('<div><span my-component ow-ref="\'hello \' + name">');
4899+
$rootScope.$apply();
4900+
48524901
$rootScope.name = 'world';
48534902
$rootScope.$apply();
48544903
expect(componentScope.owRef).toBe('hello world');
@@ -4879,6 +4928,8 @@ describe('$compile', function() {
48794928
it('should update isolate scope when "<"-bound NaN changes', inject(function() {
48804929
$rootScope.num = NaN;
48814930
compile('<div my-component ow-ref="num"></div>');
4931+
$rootScope.$apply();
4932+
48824933
var isolateScope = element.isolateScope();
48834934
expect(isolateScope.owRef).toBeNaN();
48844935

@@ -4891,6 +4942,7 @@ describe('$compile', function() {
48914942
describe('literal objects', function() {
48924943
it('should copy parent changes', inject(function() {
48934944
compile('<div><span my-component ow-ref="{name: name}">');
4945+
$rootScope.$apply();
48944946

48954947
$rootScope.name = 'a';
48964948
$rootScope.$apply();
@@ -4976,6 +5028,8 @@ describe('$compile', function() {
49765028
describe('optional one-way binding', function() {
49775029
it('should update local when origin changes', inject(function() {
49785030
compile('<div><span my-component ow-optref="name">');
5031+
$rootScope.$apply();
5032+
49795033
expect(componentScope.owOptref).toBeUndefined();
49805034
expect(componentScope.owOptrefAlias).toBe(componentScope.owOptref);
49815035

0 commit comments

Comments
 (0)