Skip to content

Commit bdd74b8

Browse files
committed
fix(ngModelController): allow $overrideModelOptions to set updateOn
Also adds more docs about "default" events and how to override ngModelController options. Closes angular#16351 Closes angular#16352
1 parent 0b6ec6b commit bdd74b8

File tree

3 files changed

+95
-7
lines changed

3 files changed

+95
-7
lines changed

src/ng/directive/ngModel.js

+30-5
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,9 @@ function NgModelController($scope, $exceptionHandler, $attr, $element, $parse, $
270270
this.$name = $interpolate($attr.name || '', false)($scope);
271271
this.$$parentForm = nullFormCtrl;
272272
this.$options = defaultModelOptions;
273+
this.$$updateEvents = '';
274+
// Attach the correct context to the event handler function for updateOn
275+
this.$$updateEventHandler = this.$$updateEventHandler.bind(this);
273276

274277
this.$$parsedNgModel = $parse($attr.ngModel);
275278
this.$$parsedNgModelAssign = this.$$parsedNgModel.assign;
@@ -875,11 +878,22 @@ NgModelController.prototype = {
875878
* See {@link ngModelOptions} for information about what options can be specified
876879
* and how model option inheritance works.
877880
*
881+
* <div class="alert alert-warning">
882+
* **Note:** this function only affects the options set on the `ngModelController`,
883+
* and not the options on the {@link ngModelOptions} directive from which they might have been
884+
* obtained initially.
885+
* </div>
886+
*
887+
* <div class="alert alert-danger">
888+
* **Note:** it is not possible to override the `getterSetter` option.
889+
* </div>
890+
*
878891
* @param {Object} options a hash of settings to override the previous options
879892
*
880893
*/
881894
$overrideModelOptions: function(options) {
882895
this.$options = this.$options.createChild(options);
896+
this.$$setUpdateOnEvents();
883897
},
884898

885899
/**
@@ -1027,6 +1041,21 @@ NgModelController.prototype = {
10271041
this.$modelValue = this.$$rawModelValue = modelValue;
10281042
this.$$parserValid = undefined;
10291043
this.$processModelValue();
1044+
},
1045+
1046+
$$setUpdateOnEvents: function() {
1047+
if (this.$$updateEvents) {
1048+
this.$$element.off(this.$$updateEvents, this.$$updateEventHandler);
1049+
}
1050+
1051+
this.$$updateEvents = this.$options.getOption('updateOn');
1052+
if (this.$$updateEvents) {
1053+
this.$$element.on(this.$$updateEvents, this.$$updateEventHandler);
1054+
}
1055+
},
1056+
1057+
$$updateEventHandler: function(ev) {
1058+
this.$$debounceViewValueCommit(ev && ev.type);
10301059
}
10311060
};
10321061

@@ -1318,11 +1347,7 @@ var ngModelDirective = ['$rootScope', function($rootScope) {
13181347
},
13191348
post: function ngModelPostLink(scope, element, attr, ctrls) {
13201349
var modelCtrl = ctrls[0];
1321-
if (modelCtrl.$options.getOption('updateOn')) {
1322-
element.on(modelCtrl.$options.getOption('updateOn'), function(ev) {
1323-
modelCtrl.$$debounceViewValueCommit(ev && ev.type);
1324-
});
1325-
}
1350+
modelCtrl.$$setUpdateOnEvents();
13261351

13271352
function setTouched() {
13281353
modelCtrl.$setTouched();

src/ng/directive/ngModelOptions.js

+28-2
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ defaultModelOptions = new ModelOptions({
177177
* `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
178178
* to have access to the updated model.
179179
*
180+
* ### Overriding immediate updates
181+
*
180182
* The following example shows how to override immediate updates. Changes on the inputs within the
181183
* form will update the model only when the control loses focus (blur event). If `escape` key is
182184
* pressed while the input field is focused, the value is reset to the value in the current model.
@@ -236,6 +238,8 @@ defaultModelOptions = new ModelOptions({
236238
* </file>
237239
* </example>
238240
*
241+
* ### Debouncing updates
242+
*
239243
* The next example shows how to debounce model changes. Model will be updated only 1 sec after last change.
240244
* If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty.
241245
*
@@ -260,6 +264,7 @@ defaultModelOptions = new ModelOptions({
260264
* </file>
261265
* </example>
262266
*
267+
*
263268
* ## Model updates and validation
264269
*
265270
* The default behaviour in `ngModel` is that the model value is set to `undefined` when the
@@ -307,20 +312,41 @@ defaultModelOptions = new ModelOptions({
307312
* You can specify the timezone that date/time input directives expect by providing its name in the
308313
* `timezone` property.
309314
*
315+
*
316+
* ## Programmatically changing options
317+
*
318+
* The `ngModelOptions` expression is only evaluated once when the directive is linked; it is not
319+
* watched for changes. However, it is possible to override the options on a single
320+
* {@link ngModel.NgModelController} instance with
321+
* {@link ngModel.NgModelController#$overrideModelOptions `NgModelController#$overrideModelOptions()`}.
322+
*
323+
*
310324
* @param {Object} ngModelOptions options to apply to {@link ngModel} directives on this element and
311325
* and its descendents. Valid keys are:
312326
* - `updateOn`: string specifying which event should the input be bound to. You can set several
313327
* events using an space delimited list. There is a special event called `default` that
314-
* matches the default events belonging to the control.
328+
* matches the default events belonging to the control. These are the events that are bound to
329+
* the control, and when fired, update the `$viewValue` via `$setViewValue`.
330+
*
331+
* `ngModelOptions` considers every event that is not listed in `updateOn` a "default" event,
332+
* since different control types use different default events.
333+
*
334+
* See also the section {@link ngModelOptions#triggering-and-debouncing-model-updates
335+
* Triggering and debouncing model updates}.
336+
*
315337
* - `debounce`: integer value which contains the debounce model update value in milliseconds. A
316338
* value of 0 triggers an immediate update. If an object is supplied instead, you can specify a
317339
* custom value for each event. For example:
318340
* ```
319341
* ng-model-options="{
320-
* updateOn: 'default blur',
342+
* updateOn: 'default blur click',
321343
* debounce: { 'default': 500, 'blur': 0 }
322344
* }"
323345
* ```
346+
*
347+
* "default" also applies to all events that are listed in `updateOn` but are not
348+
* listed in `debounce`, i.e. "click" would also be debounced by 500 milliseconds.
349+
*
324350
* - `allowInvalid`: boolean value which indicates that the model can be set with values that did
325351
* not validate correctly instead of the default behavior of setting the model to undefined.
326352
* - `getterSetter`: boolean value which determines whether or not to treat functions bound to

test/ng/directive/ngModelOptionsSpec.js

+37
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,43 @@ describe('ngModelOptions', function() {
391391
browserTrigger(inputElm[2], 'click');
392392
expect($rootScope.color).toBe('blue');
393393
});
394+
395+
it('should re-set the trigger events when overridden with $overrideModelOptions', function() {
396+
var inputElm = helper.compileInput(
397+
'<input type="text" ng-model="name" name="alias" ' +
398+
'ng-model-options="{ updateOn: \'blur click\' }"' +
399+
'/>');
400+
401+
var ctrl = inputElm.controller('ngModel');
402+
403+
helper.changeInputValueTo('a');
404+
expect($rootScope.name).toBeUndefined();
405+
browserTrigger(inputElm, 'blur');
406+
expect($rootScope.name).toEqual('a');
407+
408+
helper.changeInputValueTo('b');
409+
expect($rootScope.name).toBe('a');
410+
browserTrigger(inputElm, 'click');
411+
expect($rootScope.name).toEqual('b');
412+
413+
$rootScope.$apply('name = undefined');
414+
expect(inputElm.val()).toBe('');
415+
ctrl.$overrideModelOptions({updateOn: 'blur mousedown'});
416+
417+
helper.changeInputValueTo('a');
418+
expect($rootScope.name).toBeUndefined();
419+
browserTrigger(inputElm, 'blur');
420+
expect($rootScope.name).toEqual('a');
421+
422+
helper.changeInputValueTo('b');
423+
expect($rootScope.name).toBe('a');
424+
browserTrigger(inputElm, 'click');
425+
expect($rootScope.name).toBe('a');
426+
427+
browserTrigger(inputElm, 'mousedown');
428+
expect($rootScope.name).toEqual('b');
429+
});
430+
394431
});
395432

396433

0 commit comments

Comments
 (0)