diff --git a/src/ng/directive/ngModel.js b/src/ng/directive/ngModel.js
index 8afa3da7f64a..2dbede979fd5 100644
--- a/src/ng/directive/ngModel.js
+++ b/src/ng/directive/ngModel.js
@@ -270,6 +270,9 @@ function NgModelController($scope, $exceptionHandler, $attr, $element, $parse, $
this.$name = $interpolate($attr.name || '', false)($scope);
this.$$parentForm = nullFormCtrl;
this.$options = defaultModelOptions;
+ this.$$updateEvents = '';
+ // Attach the correct context to the event handler function for updateOn
+ this.$$updateEventHandler = this.$$updateEventHandler.bind(this);
this.$$parsedNgModel = $parse($attr.ngModel);
this.$$parsedNgModelAssign = this.$$parsedNgModel.assign;
@@ -877,11 +880,22 @@ NgModelController.prototype = {
* See {@link ngModelOptions} for information about what options can be specified
* and how model option inheritance works.
*
+ *
+ * **Note:** this function only affects the options set on the `ngModelController`,
+ * and not the options on the {@link ngModelOptions} directive from which they might have been
+ * obtained initially.
+ *
+ *
+ *
+ * **Note:** it is not possible to override the `getterSetter` option.
+ *
+ *
* @param {Object} options a hash of settings to override the previous options
*
*/
$overrideModelOptions: function(options) {
this.$options = this.$options.createChild(options);
+ this.$$setUpdateOnEvents();
},
/**
@@ -1029,6 +1043,21 @@ NgModelController.prototype = {
this.$modelValue = this.$$rawModelValue = modelValue;
this.$$parserValid = undefined;
this.$processModelValue();
+ },
+
+ $$setUpdateOnEvents: function() {
+ if (this.$$updateEvents) {
+ this.$$element.off(this.$$updateEvents, this.$$updateEventHandler);
+ }
+
+ this.$$updateEvents = this.$options.getOption('updateOn');
+ if (this.$$updateEvents) {
+ this.$$element.on(this.$$updateEvents, this.$$updateEventHandler);
+ }
+ },
+
+ $$updateEventHandler: function(ev) {
+ this.$$debounceViewValueCommit(ev && ev.type);
}
};
@@ -1320,11 +1349,7 @@ var ngModelDirective = ['$rootScope', function($rootScope) {
},
post: function ngModelPostLink(scope, element, attr, ctrls) {
var modelCtrl = ctrls[0];
- if (modelCtrl.$options.getOption('updateOn')) {
- element.on(modelCtrl.$options.getOption('updateOn'), function(ev) {
- modelCtrl.$$debounceViewValueCommit(ev && ev.type);
- });
- }
+ modelCtrl.$$setUpdateOnEvents();
function setTouched() {
modelCtrl.$setTouched();
diff --git a/src/ng/directive/ngModelOptions.js b/src/ng/directive/ngModelOptions.js
index 2defcee0d128..03c7e5945b5c 100644
--- a/src/ng/directive/ngModelOptions.js
+++ b/src/ng/directive/ngModelOptions.js
@@ -177,6 +177,8 @@ defaultModelOptions = new ModelOptions({
* `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
* to have access to the updated model.
*
+ * ### Overriding immediate updates
+ *
* The following example shows how to override immediate updates. Changes on the inputs within the
* form will update the model only when the control loses focus (blur event). If `escape` key is
* 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({
*
*
*
+ * ### Debouncing updates
+ *
* The next example shows how to debounce model changes. Model will be updated only 1 sec after last change.
* If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty.
*
@@ -260,6 +264,106 @@ defaultModelOptions = new ModelOptions({
*
*
*
+ * ### Default events, extra triggers, and catch-all debounce values
+ *
+ * This example shows the relationship between "default" update events and
+ * additional `updateOn` triggers.
+ *
+ * `default` events are those that are bound to the control, and when fired, update the `$viewValue`
+ * via {@link ngModel.NgModelController#$setViewValue $setViewValue}. Every event that is not listed
+ * in `updateOn` is considered a "default" event, since different control types have different
+ * default events.
+ *
+ * The control in this example updates by "default", "click", and "blur", with different `debounce`
+ * values. You can see that "click" doesn't have an individual `debounce` value -
+ * therefore it uses the `*` debounce value.
+ *
+ * There is also a button that calls {@link ngModel.NgModelController#$setViewValue $setViewValue}
+ * directly with a "custom" event. Since "custom" is not defined in the `updateOn` list,
+ * it is considered a "default" event and will update the
+ * control if "default" is defined in `updateOn`, and will receive the "default" debounce value.
+ * Note that this is just to illustrate how custom controls would possibly call `$setViewValue`.
+ *
+ * You can change the `updateOn` and `debounce` configuration to test different scenarios. This
+ * is done with {@link ngModel.NgModelController#$overrideModelOptions $overrideModelOptions}.
+ *
+
+
+
+
+
+ angular.module('optionsExample', [])
+ .component('modelUpdateDemo', {
+ templateUrl: 'template.html',
+ controller: function() {
+ this.name = 'Chinua';
+
+ this.options = {
+ updateOn: 'default blur click',
+ debounce: {
+ default: 2000,
+ blur: 0,
+ '*': 1000
+ }
+ };
+
+ this.updateEvents = function() {
+ var eventList = this.options.updateOn.split(' ');
+ eventList.push('*');
+ var events = {};
+
+ for (var i = 0; i < eventList.length; i++) {
+ events[eventList[i]] = this.options.debounce[eventList[i]];
+ }
+
+ this.events = events;
+ };
+
+ this.updateOptions = function() {
+ var options = angular.extend(this.options, {
+ updateOn: Object.keys(this.events).join(' ').replace('*', ''),
+ debounce: this.events
+ });
+
+ this.form.input.$overrideModelOptions(options);
+ };
+
+ // Initialize the event form
+ this.updateEvents();
+ }
+ });
+
+
+
+ Model: {{$ctrl.name}}
+
+
+
+
+
+
+
+ *
+ *
* ## Model updates and validation
*
* The default behaviour in `ngModel` is that the model value is set to `undefined` when the
@@ -307,11 +411,30 @@ defaultModelOptions = new ModelOptions({
* You can specify the timezone that date/time input directives expect by providing its name in the
* `timezone` property.
*
+ *
+ * ## Programmatically changing options
+ *
+ * The `ngModelOptions` expression is only evaluated once when the directive is linked; it is not
+ * watched for changes. However, it is possible to override the options on a single
+ * {@link ngModel.NgModelController} instance with
+ * {@link ngModel.NgModelController#$overrideModelOptions}. See also the example for
+ * {@link ngModelOptions#default-events-extra-triggers-and-catch-all-debounce-values
+ * Default events, extra triggers, and catch-all debounce values}.
+ *
+ *
* @param {Object} ngModelOptions options to apply to {@link ngModel} directives on this element and
* and its descendents. Valid keys are:
* - `updateOn`: string specifying which event should the input be bound to. You can set several
* events using an space delimited list. There is a special event called `default` that
- * matches the default events belonging to the control.
+ * matches the default events belonging to the control. These are the events that are bound to
+ * the control, and when fired, update the `$viewValue` via `$setViewValue`.
+ *
+ * `ngModelOptions` considers every event that is not listed in `updateOn` a "default" event,
+ * since different control types use different default events.
+ *
+ * See also the section {@link ngModelOptions#triggering-and-debouncing-model-updates
+ * Triggering and debouncing model updates}.
+ *
* - `debounce`: integer value which contains the debounce model update value in milliseconds. A
* value of 0 triggers an immediate update. If an object is supplied instead, you can specify a
* custom value for each event. For example:
diff --git a/test/ng/directive/ngModelOptionsSpec.js b/test/ng/directive/ngModelOptionsSpec.js
index 3814ba2bc5ca..09a9ad5f4a7c 100644
--- a/test/ng/directive/ngModelOptionsSpec.js
+++ b/test/ng/directive/ngModelOptionsSpec.js
@@ -391,6 +391,43 @@ describe('ngModelOptions', function() {
browserTrigger(inputElm[2], 'click');
expect($rootScope.color).toBe('blue');
});
+
+ it('should re-set the trigger events when overridden with $overrideModelOptions', function() {
+ var inputElm = helper.compileInput(
+ '');
+
+ var ctrl = inputElm.controller('ngModel');
+
+ helper.changeInputValueTo('a');
+ expect($rootScope.name).toBeUndefined();
+ browserTrigger(inputElm, 'blur');
+ expect($rootScope.name).toEqual('a');
+
+ helper.changeInputValueTo('b');
+ expect($rootScope.name).toBe('a');
+ browserTrigger(inputElm, 'click');
+ expect($rootScope.name).toEqual('b');
+
+ $rootScope.$apply('name = undefined');
+ expect(inputElm.val()).toBe('');
+ ctrl.$overrideModelOptions({updateOn: 'blur mousedown'});
+
+ helper.changeInputValueTo('a');
+ expect($rootScope.name).toBeUndefined();
+ browserTrigger(inputElm, 'blur');
+ expect($rootScope.name).toEqual('a');
+
+ helper.changeInputValueTo('b');
+ expect($rootScope.name).toBe('a');
+ browserTrigger(inputElm, 'click');
+ expect($rootScope.name).toBe('a');
+
+ browserTrigger(inputElm, 'mousedown');
+ expect($rootScope.name).toEqual('b');
+ });
+
});