diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js
index 527e1c355c92..6a700ec552e0 100644
--- a/src/ng/directive/input.js
+++ b/src/ng/directive/input.js
@@ -2037,12 +2037,23 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
ctrl.$modelValue = ngModelGet();
}
var prevModelValue = ctrl.$modelValue;
+ var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
+ if (allowInvalid) {
+ ctrl.$modelValue = modelValue;
+ writeToModelIfNeeded();
+ }
ctrl.$$runValidators(parserValid, modelValue, viewValue, function() {
- ctrl.$modelValue = ctrl.$valid ? modelValue : undefined;
+ if (!allowInvalid) {
+ ctrl.$modelValue = ctrl.$valid ? modelValue : undefined;
+ writeToModelIfNeeded();
+ }
+ });
+
+ function writeToModelIfNeeded() {
if (ctrl.$modelValue !== prevModelValue) {
ctrl.$$writeModelToScope();
}
- });
+ }
};
this.$$writeModelToScope = function() {
@@ -2764,6 +2775,8 @@ var ngValueDirective = function() {
* value of 0 triggers an immediate update. If an object is supplied instead, you can specify a
* custom value for each event. For example:
* `ng-model-options="{ updateOn: 'default blur', debounce: {'default': 500, 'blur': 0} }"`
+ * - `allowInvalid`: boolean value which indicates that the model can be set with values that did
+ * not validate correctly instead of the default behavior of setting the model to undefined.
* - `getterSetter`: boolean value which determines whether or not to treat functions bound to
`ngModel` as getters/setters.
* - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for
diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js
index 33dcbd688c0c..169c8d6da9b7 100644
--- a/test/ng/directive/inputSpec.js
+++ b/test/ng/directive/inputSpec.js
@@ -1769,6 +1769,79 @@ describe('input', function() {
'ng-model-options="{ getterSetter: true }" />');
});
+ it('should assign invalid values to the scope if allowInvalid is true', function() {
+ compileInput('');
+ changeInputValueTo('12345');
+
+ expect(scope.value).toBe('12345');
+ expect(inputElm).toBeInvalid();
+ });
+
+ it('should not assign not parsable values to the scope if allowInvalid is true', function() {
+ compileInput('', {
+ valid: false,
+ badInput: true
+ });
+ changeInputValueTo('abcd');
+
+ expect(scope.value).toBeUndefined();
+ expect(inputElm).toBeInvalid();
+ });
+
+ it('should update the scope before async validators execute if allowInvalid is true', inject(function($q) {
+ compileInput('');
+ var defer;
+ scope.form.input.$asyncValidators.promiseValidator = function(value) {
+ defer = $q.defer();
+ return defer.promise;
+ };
+ changeInputValueTo('12345');
+
+ expect(scope.value).toBe('12345');
+ expect(scope.form.input.$pending.promiseValidator).toBe(true);
+ defer.reject();
+ scope.$digest();
+ expect(scope.value).toBe('12345');
+ expect(inputElm).toBeInvalid();
+ }));
+
+ it('should update the view before async validators execute if allowInvalid is true', inject(function($q) {
+ compileInput('');
+ var defer;
+ scope.form.input.$asyncValidators.promiseValidator = function(value) {
+ defer = $q.defer();
+ return defer.promise;
+ };
+ scope.$apply('value = \'12345\'');
+
+ expect(inputElm.val()).toBe('12345');
+ expect(scope.form.input.$pending.promiseValidator).toBe(true);
+ defer.reject();
+ scope.$digest();
+ expect(inputElm.val()).toBe('12345');
+ expect(inputElm).toBeInvalid();
+ }));
+
+ it('should not call ng-change listeners twice if the model did not change with allowInvalid', function() {
+ compileInput('');
+ scope.changed = jasmine.createSpy('changed');
+ scope.form.input.$parsers.push(function(value) {
+ return 'modelValue';
+ });
+
+ changeInputValueTo('input1');
+ expect(scope.value).toBe('modelValue');
+ expect(scope.changed).toHaveBeenCalledOnce();
+
+ changeInputValueTo('input2');
+ expect(scope.value).toBe('modelValue');
+ expect(scope.changed).toHaveBeenCalledOnce();
+ });
});
it('should allow complex reference binding', function() {