Skip to content

Commit 9e35897

Browse files
committed
feat(ngModel): bind to getters/setters
BREAKING CHANGE Closes angular#768
1 parent 19b6b34 commit 9e35897

File tree

2 files changed

+99
-1
lines changed

2 files changed

+99
-1
lines changed

src/ng/directive/input.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1820,7 +1820,24 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
18201820
ctrl.$modelValue = ctrl.$valid ? modelValue : undefined;
18211821
ctrl.$$invalidModelValue = ctrl.$valid ? undefined : modelValue;
18221822

1823-
ngModelSet($scope, ctrl.$modelValue);
1823+
var useGetterSetter, getterSetter;
1824+
1825+
if (ctrl.$options && isDefined(ctrl.$options.getterSetter)) {
1826+
if (useGetterSetter = ctrl.$options.getterSetter) {
1827+
getterSetter = ngModelGet($scope);
1828+
}
1829+
} else {
1830+
getterSetter = ngModelGet($scope)
1831+
useGetterSetter = isFunction(getterSetter);
1832+
}
1833+
1834+
if (useGetterSetter) {
1835+
if (isFunction(getterSetter)) {
1836+
getterSetter(ctrl.$modelValue);
1837+
}
1838+
} else {
1839+
ngModelSet($scope, ctrl.$modelValue);
1840+
}
18241841
forEach(ctrl.$viewChangeListeners, function(listener) {
18251842
try {
18261843
listener();
@@ -1896,6 +1913,12 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
18961913
$scope.$watch(function ngModelWatch() {
18971914
var modelValue = ngModelGet($scope);
18981915

1916+
if (isFunction(modelValue) && ((ctrl.$options && isDefined(ctrl.$options.getterSetter)) ?
1917+
ctrl.$options.getterSetter : true)) {
1918+
1919+
modelValue = modelValue();
1920+
}
1921+
18991922
// if scope model value and ngModel value are out of sync
19001923
if (ctrl.$modelValue !== modelValue &&
19011924
(isUndefined(ctrl.$$invalidModelValue) || ctrl.$$invalidModelValue != modelValue)) {
@@ -2520,6 +2543,9 @@ var ngModelOptionsDirective = function() {
25202543
} else {
25212544
this.$options.updateOnDefault = true;
25222545
}
2546+
if (this.$options.getterSetter === 'auto') {
2547+
this.$options.getterSetter = undefined;
2548+
}
25232549
}]
25242550
};
25252551
};

test/ng/directive/inputSpec.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,30 @@ describe('input', function() {
664664
});
665665

666666

667+
it('should bind to a getter', function() {
668+
compileInput('<input type="text" ng-model="name" name="alias" ng-change="change()" />');
669+
670+
scope.$apply(function() {
671+
scope.name = function () { return 'misko'; };
672+
});
673+
674+
expect(inputElm.val()).toBe('misko');
675+
});
676+
677+
678+
it('should bind to a setter', function() {
679+
compileInput('<input type="text" ng-model="name" name="alias" ng-change="change()" />');
680+
681+
var name = '';
682+
scope.name = function (newName) {
683+
return typeof newName !== 'undefined' ? (name = newName) : name;
684+
};
685+
changeInputValueTo('adam');
686+
expect(inputElm.val()).toBe('adam');
687+
expect(name).toBe('adam');
688+
});
689+
690+
667691
it('should not set readonly or disabled property on ie7', function() {
668692
this.addMatchers({
669693
toBeOff: function(attributeName) {
@@ -1213,6 +1237,54 @@ describe('input', function() {
12131237
expect(inputElm.val()).toBe('');
12141238
}));
12151239

1240+
it('should not try to invoke a model if getterSetter is false', function() {
1241+
compileInput(
1242+
'<input type="text" ng-model="name" '+
1243+
'ng-model-options="{ getterSetter: false }" />');
1244+
1245+
var spy = scope.name = jasmine.createSpy('setterSpy');
1246+
changeInputValueTo('a');
1247+
expect(spy).not.toHaveBeenCalled();
1248+
expect(inputElm.val()).toBe('a');
1249+
});
1250+
1251+
it('should always try to invoke a model if getterSetter is true', function() {
1252+
compileInput(
1253+
'<input type="text" ng-model="name" '+
1254+
'ng-model-options="{ getterSetter: true }" />');
1255+
1256+
var spy = scope.name = jasmine.createSpy('setterSpy').andCallFake(function () {
1257+
return 'b';
1258+
});
1259+
changeInputValueTo('a');
1260+
expect(inputElm.val()).toBe('b');
1261+
expect(spy).toHaveBeenCalledWith('a');
1262+
expect(scope.name).toBe(spy);
1263+
1264+
scope.name = 'c';
1265+
changeInputValueTo('d');
1266+
expect(inputElm.val()).toBe('c');
1267+
});
1268+
1269+
it('should try to invoke a model if getterSetter is "auto" and model is a function', function($timeout) {
1270+
compileInput(
1271+
'<input type="text" ng-model="name" '+
1272+
'ng-model-options="{ getterSetter: \'auto\' }" />');
1273+
1274+
var spy = scope.name = jasmine.createSpy('setterSpy').andCallFake(function () {
1275+
return 'b';
1276+
});
1277+
changeInputValueTo('a');
1278+
expect(inputElm.val()).toBe('b');
1279+
expect(spy).toHaveBeenCalledWith('a');
1280+
expect(scope.name).toBe(spy);
1281+
1282+
scope.name = 'c';
1283+
changeInputValueTo('d');
1284+
expect(inputElm.val()).toBe('d');
1285+
expect(scope.name).toBe('d');
1286+
});
1287+
12161288
});
12171289

12181290
it('should allow complex reference binding', function() {

0 commit comments

Comments
 (0)