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

Commit 72e7adf

Browse files
committed
fix(ngModelOptions): preserve context of getter/setters
Closes #9394 BREAKING CHANGE: previously, ngModel invoked getter/setters in the global context. For example: ```js <input ng-model="model.value" ng-model-options="{ getterSetter: true }"> ``` would previously invoke `model.value()` in the global context. Now, ngModel invokes `value` with `model` as the context. It's unlikely that real apps relied on this behavior. If they did they can use `.bind` to explicilty bind a getter/getter to the global context, or just reference globals normally without `this`.
1 parent e3764e3 commit 72e7adf

File tree

2 files changed

+45
-3
lines changed

2 files changed

+45
-3
lines changed

src/ng/directive/input.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -1740,13 +1740,14 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
17401740

17411741

17421742
var parsedNgModel = $parse($attr.ngModel),
1743+
parsedNgModelContext = null,
17431744
pendingDebounce = null,
17441745
ctrl = this;
17451746

17461747
var ngModelGet = function ngModelGet() {
17471748
var modelValue = parsedNgModel($scope);
17481749
if (ctrl.$options && ctrl.$options.getterSetter && isFunction(modelValue)) {
1749-
modelValue = modelValue();
1750+
modelValue = $scope.$eval($attr.ngModel + '()');
17501751
}
17511752
return modelValue;
17521753
};
@@ -1755,8 +1756,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
17551756
var getterSetter;
17561757
if (ctrl.$options && ctrl.$options.getterSetter &&
17571758
isFunction(getterSetter = parsedNgModel($scope))) {
1758-
1759-
getterSetter(ctrl.$modelValue);
1759+
$scope.$eval($attr.ngModel + '($$$p)', {$$$p: ctrl.$modelValue});
17601760
} else {
17611761
parsedNgModel.assign($scope, ctrl.$modelValue);
17621762
}
@@ -1765,6 +1765,11 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
17651765
this.$$setOptions = function(options) {
17661766
ctrl.$options = options;
17671767

1768+
if (ctrl.$options && ctrl.$options.getterSetter && ctrl.$options.getterSetterContext) {
1769+
// Use the provided context expression to specify the context used when invoking the
1770+
// getter/setter function
1771+
parsedNgModelContext = $parse(ctrl.$options.getterSetterContext);
1772+
}
17681773
if (!parsedNgModel.assign && (!options || !options.getterSetter)) {
17691774
throw $ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}",
17701775
$attr.ngModel, startingTag($element));

test/ng/directive/inputSpec.js

+37
Original file line numberDiff line numberDiff line change
@@ -2093,6 +2093,43 @@ describe('input', function() {
20932093
'ng-model-options="{ getterSetter: true }" />');
20942094
});
20952095

2096+
it('should invoke a model in the correct context if getterSetter is true', function() {
2097+
compileInput(
2098+
'<input type="text" ng-model="someService.getterSetter" '+
2099+
'ng-model-options="{ getterSetter: true }" />');
2100+
2101+
scope.someService = {
2102+
value: 'a',
2103+
getterSetter: function(newValue) {
2104+
this.value = newValue || this.value;
2105+
return this.value;
2106+
}
2107+
};
2108+
spyOn(scope.someService, 'getterSetter').andCallThrough();
2109+
scope.$apply();
2110+
2111+
expect(inputElm.val()).toBe('a');
2112+
expect(scope.someService.getterSetter).toHaveBeenCalledWith();
2113+
expect(scope.someService.value).toBe('a');
2114+
2115+
changeInputValueTo('b');
2116+
expect(scope.someService.getterSetter).toHaveBeenCalledWith('b');
2117+
expect(scope.someService.value).toBe('b');
2118+
2119+
scope.someService.value = 'c';
2120+
scope.$apply();
2121+
expect(inputElm.val()).toBe('c');
2122+
expect(scope.someService.getterSetter).toHaveBeenCalledWith();
2123+
});
2124+
2125+
it('should fail to parse if getterSetterContext is an invalid expression', function() {
2126+
expect(function() {
2127+
compileInput(
2128+
'<input type="text" ng-model="someService.getterSetter" '+
2129+
'ng-model-options="{ getterSetter: true, getterSetterContext: \'throw error\' }" />');
2130+
}).toThrowMinErr("$parse", "syntax", "Syntax Error: Token 'error' is an unexpected token at column 7 of the expression [throw error] starting at [error].");
2131+
});
2132+
20962133
it('should assign invalid values to the scope if allowInvalid is true', function() {
20972134
compileInput('<input type="text" name="input" ng-model="value" maxlength="1" ' +
20982135
'ng-model-options="{allowInvalid: true}" />');

0 commit comments

Comments
 (0)