diff --git a/src/.jshintrc b/src/.jshintrc
index fd0170bc6661..d4912eafcf34 100644
--- a/src/.jshintrc
+++ b/src/.jshintrc
@@ -100,6 +100,7 @@
     "assertNotHasOwnProperty": false,
     "getter": false,
     "getBlockElements": false,
+    "VALIDITY_STATE_PROPERTY": false,
 
     /* AngularPublic.js */
     "version": false,
diff --git a/src/Angular.js b/src/Angular.js
index e09351d9b583..5916174504b3 100644
--- a/src/Angular.js
+++ b/src/Angular.js
@@ -13,6 +13,7 @@
     -angularModule,
     -nodeName_,
     -uid,
+    -VALIDITY_STATE_PROPERTY,
 
     -lowercase,
     -uppercase,
@@ -102,6 +103,10 @@
  * <div doc-module-components="ng"></div>
  */
 
+// The name of a form control's ValidityState property.
+// This is used so that it's possible for internal tests to create mock ValidityStates.
+var VALIDITY_STATE_PROPERTY = 'validity';
+
 /**
  * @ngdoc function
  * @name angular.lowercase
diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js
index c1eaac7062b1..951a4dfa8865 100644
--- a/src/ng/directive/input.js
+++ b/src/ng/directive/input.js
@@ -435,15 +435,29 @@ function validate(ctrl, validatorName, validity, value){
   return validity ? value : undefined;
 }
 
+function testFlags(validity, flags) {
+  var i, flag;
+  if (flags) {
+    for (i=0; i<flags.length; ++i) {
+      flag = flags[i];
+      if (validity[flag]) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
 
-function addNativeHtml5Validators(ctrl, validatorName, element) {
-  var validity = element.prop('validity');
+// Pass validity so that behaviour can be mocked easier.
+function addNativeHtml5Validators(ctrl, validatorName, badFlags, ignoreFlags, validity) {
   if (isObject(validity)) {
+    ctrl.$$hasNativeValidators = true;
     var validator = function(value) {
       // Don't overwrite previous validation, don't consider valueMissing to apply (ng-required can
       // perform the required validation)
-      if (!ctrl.$error[validatorName] && (validity.badInput || validity.customError ||
-          validity.typeMismatch) && !validity.valueMissing) {
+      if (!ctrl.$error[validatorName] &&
+          !testFlags(validity, ignoreFlags) &&
+          testFlags(validity, badFlags)) {
         ctrl.$setValidity(validatorName, false);
         return;
       }
@@ -454,8 +468,9 @@ function addNativeHtml5Validators(ctrl, validatorName, element) {
 }
 
 function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
-  var validity = element.prop('validity');
+  var validity = element.prop(VALIDITY_STATE_PROPERTY);
   var placeholder = element[0].placeholder, noevent = {};
+  ctrl.$$validityState = validity;
 
   // In composition mode, users are still inputing intermediate text buffer,
   // hold the listener until composition is done.
@@ -493,11 +508,11 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
       value = trim(value);
     }
 
-    if (ctrl.$viewValue !== value ||
-        // If the value is still empty/falsy, and there is no `required` error, run validators
-        // again. This enables HTML5 constraint validation errors to affect Angular validation
-        // even when the first character entered causes an error.
-        (validity && value === '' && !validity.valueMissing)) {
+    // If a control is suffering from bad input, browsers discard its value, so it may be
+    // necessary to revalidate even if the control's value is the same empty value twice in
+    // a row.
+    var revalidate = validity && ctrl.$$hasNativeValidators;
+    if (ctrl.$viewValue !== value || (value === '' && revalidate)) {
       if (scope.$$phase) {
         ctrl.$setViewValue(value);
       } else {
@@ -603,6 +618,8 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
   }
 }
 
+var numberBadFlags = ['badInput'];
+
 function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
   textInputType(scope, element, attr, ctrl, $sniffer, $browser);
 
@@ -617,7 +634,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
     }
   });
 
-  addNativeHtml5Validators(ctrl, 'number', element);
+  addNativeHtml5Validators(ctrl, 'number', numberBadFlags, null, ctrl.$$validityState);
 
   ctrl.$formatters.push(function(value) {
     return ctrl.$isEmpty(value) ? '' : '' + value;
diff --git a/test/.jshintrc b/test/.jshintrc
index f369426120d1..d4c3479482ff 100644
--- a/test/.jshintrc
+++ b/test/.jshintrc
@@ -101,6 +101,7 @@
     "assertNotHasOwnProperty": false,
     "getter": false,
     "getBlockElements": false,
+    "VALIDITY_STATE_PROPERTY": true,
 
     /* filters.js */
     "getFirstThursdayOfYear": false,
diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js
index d14e1a2601c0..b5c6918f23bc 100644
--- a/test/ng/directive/inputSpec.js
+++ b/test/ng/directive/inputSpec.js
@@ -410,15 +410,33 @@ describe('ngModel', function() {
 
 
 describe('input', function() {
-  var formElm, inputElm, scope, $compile, $sniffer, $browser, changeInputValueTo;
+  var formElm, inputElm, scope, $compile, $sniffer, $browser, changeInputValueTo, currentSpec;
 
-  function compileInput(inputHtml) {
+  function compileInput(inputHtml, mockValidity) {
     inputElm = jqLite(inputHtml);
+    if (isObject(mockValidity)) {
+      VALIDITY_STATE_PROPERTY = 'ngMockValidity';
+      inputElm.prop(VALIDITY_STATE_PROPERTY, mockValidity);
+      currentSpec.after(function() {
+        VALIDITY_STATE_PROPERTY = 'validity';
+      });
+    }
     formElm = jqLite('<form name="form"></form>');
     formElm.append(inputElm);
     $compile(formElm)(scope);
   }
 
+  var attrs;
+  beforeEach(function() { currentSpec = this; });
+  afterEach(function() { currentSpec = null; });
+  beforeEach(module(function($compileProvider) {
+    $compileProvider.directive('attrCapture', function() {
+      return function(scope, element, $attrs) {
+        attrs = $attrs;
+      };
+    });
+  }));
+
   beforeEach(inject(function($injector, _$sniffer_, _$browser_) {
     $sniffer = _$sniffer_;
     $browser = _$browser_;
@@ -844,6 +862,33 @@ describe('input', function() {
     });
 
 
+    it('should invalidate number if suffering from bad input', function() {
+      compileInput('<input type="number" ng-model="age" />', {
+        valid: false,
+        badInput: true
+      });
+
+      changeInputValueTo('10a');
+      expect(scope.age).toBeUndefined();
+      expect(inputElm).toBeInvalid();
+    });
+
+
+    it('should validate number if transition from bad input to empty string', function() {
+      var validity = {
+        valid: false,
+        badInput: true
+      };
+      compileInput('<input type="number" ng-model="age" />', validity);
+      changeInputValueTo('10a');
+      validity.badInput = false;
+      validity.valid = true;
+      changeInputValueTo('');
+      expect(scope.age).toBeNull();
+      expect(inputElm).toBeValid();
+    });
+
+
     describe('min', function() {
 
       it('should validate', function() {