diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js
index fa6fe55d9f5e..b7823961491b 100644
--- a/src/ng/directive/input.js
+++ b/src/ng/directive/input.js
@@ -1757,7 +1757,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* Runs each of the registered validations set on the $validators object.
*/
this.$validate = function() {
- this.$$runValidators(ctrl.$modelValue, ctrl.$viewValue);
+ this.$$runValidators(ctrl.$$validateValue, ctrl.$viewValue);
};
this.$$runValidators = function(modelValue, viewValue) {
@@ -1766,26 +1766,9 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
});
};
- /**
- * @ngdoc method
- * @name ngModel.NgModelController#$commitViewValue
- *
- * @description
- * Commit a pending update to the `$modelValue`.
- *
- * Updates may be pending by a debounced event or because the input is waiting for a some future
- * event defined in `ng-model-options`. this method is rarely needed as `NgModelController`
- * usually handles calling this in response to input events.
- */
- this.$commitViewValue = function() {
+ this.$$validateViewValue = function() {
var viewValue = ctrl.$viewValue;
- $timeout.cancel(pendingDebounce);
- if (ctrl.$$lastCommittedViewValue === viewValue) {
- return;
- }
- ctrl.$$lastCommittedViewValue = viewValue;
-
// change to dirty
if (ctrl.$pristine) {
ctrl.$dirty = true;
@@ -1804,9 +1787,38 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
(isUndefined(ctrl.$$invalidModelValue) || ctrl.$$invalidModelValue != modelValue)) {
ctrl.$$runValidators(modelValue, viewValue);
- ctrl.$modelValue = ctrl.$valid ? modelValue : undefined;
+ ctrl.$$validateValue = ctrl.$valid ? modelValue : undefined;
ctrl.$$invalidModelValue = ctrl.$valid ? undefined : modelValue;
+ return ctrl.$valid ? modelValue : undefined;
+ }
+
+ return ctrl.$modelValue;
+ };
+
+ /**
+ * @ngdoc method
+ * @name ngModel.NgModelController#$commitViewValue
+ *
+ * @description
+ * Commit a pending update to the `$modelValue`.
+ *
+ * Updates may be pending by a debounced event or because the input is waiting for a some future
+ * event defined in `ng-model-options`. this method is rarely needed as `NgModelController`
+ * usually handles calling this in response to input events.
+ */
+ this.$commitViewValue = function() {
+ var viewValue = ctrl.$viewValue;
+
+ $timeout.cancel(pendingDebounce);
+ if (ctrl.$$lastCommittedViewValue === viewValue) {
+ return;
+ }
+ ctrl.$$lastCommittedViewValue = viewValue;
+
+ var modelValue = ctrl.$$validateViewValue();
+ if (ctrl.$modelValue !== modelValue) {
+ ctrl.$modelValue = modelValue;
ngModelSet($scope, ctrl.$modelValue);
forEach(ctrl.$viewChangeListeners, function(listener) {
try {
@@ -1848,6 +1860,9 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
*/
this.$setViewValue = function(value, trigger) {
ctrl.$viewValue = value;
+ if (ctrl.$options && ctrl.$options.validateOnDefault) {
+ ctrl.$$validateViewValue();
+ }
if (!ctrl.$options || ctrl.$options.updateOnDefault) {
ctrl.$$debounceViewValueCommit(trigger);
}
@@ -1897,6 +1912,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
ctrl.$$runValidators(modelValue, viewValue);
ctrl.$modelValue = ctrl.$valid ? modelValue : undefined;
+ ctrl.$$validateValue = ctrl.$modelValue;
ctrl.$$invalidModelValue = ctrl.$valid ? undefined : modelValue;
if (ctrl.$viewValue !== viewValue) {
@@ -2048,6 +2064,13 @@ var ngModelDirective = function() {
});
});
}
+ if (modelCtrl.$options && modelCtrl.$options.validateOn) {
+ element.on(modelCtrl.$options.validateOn, function(ev) {
+ scope.$apply(function() {
+ modelCtrl.$$validateViewValue();
+ });
+ });
+ }
element.on('blur', function(ev) {
scope.$apply(function() {
@@ -2504,6 +2527,13 @@ var ngModelOptionsDirective = function() {
that.$options.updateOnDefault = true;
return ' ';
}));
+
+ if (this.$options.validateOn !== undefined) {
+ this.$options.validateOn = trim(this.$options.validateOn.replace(DEFAULT_REGEXP, function() {
+ that.$options.validateOnDefault = true;
+ return ' ';
+ }));
+ }
} else {
this.$options.updateOnDefault = true;
}
diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js
index e48a2a082672..310d83b8f3b5 100644
--- a/test/ng/directive/inputSpec.js
+++ b/test/ng/directive/inputSpec.js
@@ -293,12 +293,12 @@ describe('NgModelController', function() {
return (/^[A-Z]+$/).test(value);
};
- ctrl.$modelValue = 'test';
+ ctrl.$$validateValue = 'test';
ctrl.$validate();
expect(ctrl.$valid).toBe(false);
- ctrl.$modelValue = 'TEST';
+ ctrl.$$validateValue = 'TEST';
ctrl.$validate();
expect(ctrl.$valid).toBe(true);
@@ -309,12 +309,12 @@ describe('NgModelController', function() {
return (/^[A-Z]+$/).test(value);
};
- ctrl.$modelValue = 'test';
+ ctrl.$$validateValue = 'test';
ctrl.$validate();
expect(ctrl.$valid).toBe(false);
- ctrl.$modelValue = 'TEST';
+ ctrl.$$validateValue = 'TEST';
ctrl.$validate();
expect(ctrl.$valid).toBe(true);
@@ -846,6 +846,36 @@ describe('input', function() {
expect(scope.name).toEqual('a');
});
+ it('should allow validating before view value is committed', function() {
+ scope.value = 2;
+ compileInput(
+ '');
+ changeInputValueTo('20');
+ expect(scope.form.alias.$error.max).toBeTruthy();
+ expect(formElm).toBeDirty();
+ expect(scope.value).toEqual(2);
+ browserTrigger(inputElm, 'blur');
+ expect(scope.value).toBeUndefined();
+ });
+
+ it('should allow defining trigger for validation', function() {
+ scope.value = 2;
+ compileInput(
+ '');
+ changeInputValueTo('20');
+ expect(scope.form.alias.$error.max).toBeFalsy();
+ browserTrigger(inputElm, 'blur');
+ expect(scope.form.alias.$error.max).toBeTruthy();
+ expect(formElm).toBeDirty();
+ expect(scope.value).toEqual(2);
+ browserTrigger(formElm, 'submit');
+ expect(scope.value).toBeUndefined();
+ });
+
it('should not dirty the input if nothing was changed before updateOn trigger', function() {
compileInput(
'