diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js index 1b393676cd79..2a6adadaf6a9 100644 --- a/src/ng/directive/input.js +++ b/src/ng/directive/input.js @@ -1716,13 +1716,14 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ var parsedNgModel = $parse($attr.ngModel), + parsedNgModelContext = null, pendingDebounce = null, ctrl = this; var ngModelGet = function ngModelGet() { var modelValue = parsedNgModel($scope); if (ctrl.$options && ctrl.$options.getterSetter && isFunction(modelValue)) { - modelValue = modelValue(); + modelValue = modelValue.call(parsedNgModelContext ? parsedNgModelContext($scope) : $scope); } return modelValue; }; @@ -1732,7 +1733,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ if (ctrl.$options && ctrl.$options.getterSetter && isFunction(getterSetter = parsedNgModel($scope))) { - getterSetter(ctrl.$modelValue); + getterSetter.call(parsedNgModelContext ? parsedNgModelContext($scope) : $scope, ctrl.$modelValue); } else { parsedNgModel.assign($scope, ctrl.$modelValue); } @@ -1741,6 +1742,11 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ this.$$setOptions = function(options) { ctrl.$options = options; + if (ctrl.$options && ctrl.$options.getterSetter && ctrl.$options.getterSetterContext) { + // Use the provided context expression to specify the context used when invoking the + // getter/setter function + parsedNgModelContext = $parse(ctrl.$options.getterSetterContext); + } if (!parsedNgModel.assign && (!options || !options.getterSetter)) { throw $ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}", $attr.ngModel, startingTag($element)); diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js index 8d4e47763609..565073e05379 100644 --- a/test/ng/directive/inputSpec.js +++ b/test/ng/directive/inputSpec.js @@ -1975,6 +1975,83 @@ describe('input', function() { 'ng-model-options="{ getterSetter: true }" />'); }); + it('should try to invoke a model with default context if getterSetter is true and getterSetterContext is not provided', function() { + scope.value = 'scopeContext'; + compileInput( + ''); + + scope.someService = { + value: 'b', + getterSetter: function(newValue) { + this.value = newValue || this.value; + return this.value; + } + }; + spyOn(scope.someService, 'getterSetter').andCallThrough(); + scope.$apply(); + expect(inputElm.val()).toBe('scopeContext'); + expect(scope.someService.getterSetter).toHaveBeenCalledWith(); + expect(scope.someService.value).toBe('b'); // 'this' is not bound to the service w/o ngModelContext + expect(scope.value).toBe('scopeContext'); + + changeInputValueTo('a'); + expect(scope.someService.getterSetter).toHaveBeenCalledWith('a'); + expect(scope.someService.value).toBe('b'); + expect(scope.value).toBe('a'); + + scope.someService.value = 'c'; + scope.$apply(); + expect(inputElm.val()).toBe('a'); + expect(scope.someService.getterSetter).toHaveBeenCalledWith(); + expect(scope.someService.value).toBe('c'); + expect(scope.value).toBe('a'); + + scope.value = 'd'; + scope.$apply(); + expect(inputElm.val()).toBe('d'); + expect(scope.someService.getterSetter).toHaveBeenCalledWith(); + expect(scope.someService.value).toBe('c'); + expect(scope.value).toBe('d'); + }); + + it('should try to invoke a model with the provided context if getterSetter is true and getterSetterContext is an expression', function() { + compileInput( + ''); + + scope.someService = { + value: 'b', + getterSetter: function(newValue) { + this.value = newValue || this.value; + return this.value; + } + }; + spyOn(scope.someService, 'getterSetter').andCallThrough(); + scope.$apply(); + expect(inputElm.val()).toBe('b'); + expect(scope.someService.getterSetter).toHaveBeenCalledWith(); + expect(scope.someService.value).toBe('b'); + + changeInputValueTo('a'); + expect(scope.someService.getterSetter).toHaveBeenCalledWith('a'); + expect(scope.someService.value).toBe('a'); + + scope.someService.value = 'c'; + scope.$apply(); + expect(inputElm.val()).toBe('c'); + expect(scope.someService.getterSetter).toHaveBeenCalledWith(); + expect(scope.someService.value).toBe('c'); + }); + + it('should fail to parse if getterSetterContext is an invalid expression', function() { + expect(function() { + compileInput( + ''); + }).toThrowMinErr("$parse", "syntax", "Syntax Error: Token 'error' is an unexpected token at column 7 of the expression [throw error] starting at [error]."); + }); + it('should assign invalid values to the scope if allowInvalid is true', function() { compileInput('');