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

Commit aec297c

Browse files
committed
fix($compile): be more careful about shadowing $attrs values
Shadows only when attributes are non-optional and not own properties, only stores the observed '@' values when they are indeed strings. Partial revert of 531a8de
1 parent 46b7cf7 commit aec297c

File tree

2 files changed

+69
-27
lines changed

2 files changed

+69
-27
lines changed

src/ng/compile.js

+12-15
Original file line numberDiff line numberDiff line change
@@ -2566,36 +2566,32 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
25662566
lastValue,
25672567
parentGet, parentSet, compare;
25682568

2569-
if (!hasOwnProperty.call(attrs, attrName)) {
2570-
// In the case of user defined a binding with the same name as a method in Object.prototype but didn't set
2571-
// the corresponding attribute. We need to make sure subsequent code won't access to the prototype function
2572-
attrs[attrName] = undefined;
2573-
}
2574-
25752569
switch (mode) {
25762570

25772571
case '@':
2578-
if (!attrs[attrName] && !optional) {
2579-
destination[scopeName] = undefined;
2572+
if (!optional && !hasOwnProperty.call(attrs, attrName)) {
2573+
destination[scopeName] = attrs[attrName] = void 0;
25802574
}
2581-
25822575
attrs.$observe(attrName, function(value) {
2583-
destination[scopeName] = value;
2576+
if (isString(value)) {
2577+
destination[scopeName] = value;
2578+
}
25842579
});
25852580
attrs.$$observers[attrName].$$scope = scope;
2586-
if (attrs[attrName]) {
2581+
if (isString(attrs[attrName])) {
25872582
// If the attribute has been provided then we trigger an interpolation to ensure
25882583
// the value is there for use in the link fn
25892584
destination[scopeName] = $interpolate(attrs[attrName])(scope);
25902585
}
25912586
break;
25922587

25932588
case '=':
2594-
if (optional && !attrs[attrName]) {
2595-
return;
2589+
if (!hasOwnProperty.call(attrs, attrName)) {
2590+
if (optional) break;
2591+
attrs[attrName] = void 0;
25962592
}
2597-
parentGet = $parse(attrs[attrName]);
25982593

2594+
parentGet = $parse(attrs[attrName]);
25992595
if (parentGet.literal) {
26002596
compare = equals;
26012597
} else {
@@ -2634,7 +2630,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
26342630
break;
26352631

26362632
case '&':
2637-
parentGet = $parse(attrs[attrName]);
2633+
// Don't assign Object.prototype method to scope
2634+
parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop
26382635

26392636
// Don't assign noop to destination if expression is not valid
26402637
if (parentGet === noop && optional) break;

test/ng/compileSpec.js

+57-12
Original file line numberDiff line numberDiff line change
@@ -2521,10 +2521,17 @@ describe('$compile', function() {
25212521
};
25222522

25232523
expect(func).not.toThrow();
2524-
expect(element.find('span').scope()).toBe(element.isolateScope());
2525-
expect(element.isolateScope()).not.toBe($rootScope);
2526-
expect(element.isolateScope()['constructor']).toBe($rootScope.constructor);
2527-
expect(element.isolateScope()['valueOf']).toBeUndefined();
2524+
var scope = element.isolateScope();
2525+
expect(element.find('span').scope()).toBe(scope);
2526+
expect(scope).not.toBe($rootScope);
2527+
2528+
// Not shadowed because optional
2529+
expect(scope.constructor).toBe($rootScope.constructor);
2530+
expect(scope.hasOwnProperty('constructor')).toBe(false);
2531+
2532+
// Shadowed with undefined because not optional
2533+
expect(scope.valueOf).toBeUndefined();
2534+
expect(scope.hasOwnProperty('valueOf')).toBe(true);
25282535
})
25292536
);
25302537

@@ -2539,10 +2546,13 @@ describe('$compile', function() {
25392546
};
25402547

25412548
expect(func).not.toThrow();
2542-
expect(element.find('span').scope()).toBe(element.isolateScope());
2543-
expect(element.isolateScope()).not.toBe($rootScope);
2544-
expect(element.isolateScope()['constructor']).toBe('constructor');
2545-
expect(element.isolateScope()['valueOf']).toBe('valueOf');
2549+
var scope = element.isolateScope();
2550+
expect(element.find('span').scope()).toBe(scope);
2551+
expect(scope).not.toBe($rootScope);
2552+
expect(scope.constructor).toBe('constructor');
2553+
expect(scope.hasOwnProperty('constructor')).toBe(true);
2554+
expect(scope.valueOf).toBe('valueOf');
2555+
expect(scope.hasOwnProperty('valueOf')).toBe(true);
25462556
})
25472557
);
25482558

@@ -2553,10 +2563,17 @@ describe('$compile', function() {
25532563
};
25542564

25552565
expect(func).not.toThrow();
2556-
expect(element.find('span').scope()).toBe(element.isolateScope());
2557-
expect(element.isolateScope()).not.toBe($rootScope);
2558-
expect(element.isolateScope()['constructor']).toBe($rootScope.constructor);
2559-
expect(element.isolateScope()['valueOf']).toBeUndefined();
2566+
var scope = element.isolateScope();
2567+
expect(element.find('span').scope()).toBe(scope);
2568+
expect(scope).not.toBe($rootScope);
2569+
2570+
// Does not shadow value because optional
2571+
expect(scope.constructor).toBe($rootScope.constructor);
2572+
expect(scope.hasOwnProperty('constructor')).toBe(false);
2573+
2574+
// Shadows value because not optional
2575+
expect(scope.valueOf).toBeUndefined();
2576+
expect(scope.hasOwnProperty('valueOf')).toBe(true);
25602577
})
25612578
);
25622579

@@ -4415,6 +4432,34 @@ describe('$compile', function() {
44154432
childScope.theCtrl.test();
44164433
});
44174434
});
4435+
4436+
it('should not overwrite @-bound property each digest when not present', function() {
4437+
module(function($compileProvider) {
4438+
$compileProvider.directive('testDir', valueFn({
4439+
scope: {},
4440+
bindToController: {
4441+
prop: '@'
4442+
},
4443+
controller: function() {
4444+
var self = this;
4445+
this.prop = this.prop || 'default';
4446+
this.getProp = function() {
4447+
return self.prop;
4448+
};
4449+
},
4450+
controllerAs: 'ctrl',
4451+
template: '<p></p>'
4452+
}));
4453+
});
4454+
inject(function($compile, $rootScope) {
4455+
element = $compile('<div test-dir></div>')($rootScope);
4456+
var scope = element.isolateScope();
4457+
expect(scope.ctrl.getProp()).toBe('default');
4458+
4459+
$rootScope.$digest();
4460+
expect(scope.ctrl.getProp()).toBe('default');
4461+
});
4462+
});
44184463
});
44194464

44204465

0 commit comments

Comments
 (0)