<!DOCTYPE html>
<html ng-app='exampleModule'>

  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.18/angular.min.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="script.js"></script>
  </head>

  <body ng-controller="MainCtrl">
    <form name="form">
        <p>The two inputs below have <strong>ng-maxlength="5"</strong></p>
        <hr/>
        <p>With the <strong>validity-bind-fix</strong> directive <input type="text" ng-model="bar" validity-bind-fix ng-maxlength="5" /></p>
        <p>Model value: {{bar}}</p>
        <hr/>
        <p>Without the directive: <input type="text" ng-model="foo" ng-maxlength="5" /></p>
        <p>Model value: {{foo}}</p>
        <hr/>
        <p>Click this button to set invalid model values <button ng-click="invalidate()">"1234567890"</button></p>
    </form>
  </body>
</html>


angular.module('exampleModule', ['cxForm'])

.controller('MainCtrl', ['$scope', function ($scope) {
    $scope.foo = 'abcdef';
    $scope.bar = 'abcdef';
    $scope.invalidate = function() {
        $scope.foo = '1234567890';
        $scope.bar = '1234567890';
    };
}]);

/**
 * @ngdoc cxForm
 * @name cxForm
 * @description
 * Form add-ons and fixes
 */
angular.module('cxForm', [])

/**
 * @ngdoc directive
 * @name validityBindFix
 * @module cxForm
 *
 * @description
 * Prevents ngModelController from nulling the model value when it's set invalid by some rule.
 * Works in both directions, making sure an invalid model value is copied into the view value and making sure an invalid
 * model value is copied into the model. **Warning:** totally bypasses formatters/parsers when invalid, but probably good 
 * enough to use in most cases, like maxlength or pattern.
 *
 * See angular issue: https://github.com/angular/angular.js/issues/1412
 *
 * Inspired by the strategy provided by Emil van Galen here:
 * http://blog.jdriven.com/2013/09/how-angularjs-directives-renders-model-value-and-parses-user-input/
 *
 * @restrict A
 * @scope
 *
 * @param {object} ngModel Required `ng-model` value. If not present in the same element an error occurs.
 */
.directive('validityBindFix', function () {
    'use strict';

    return {
        require: '?ngModel',
        priority: 9999,
        restrict: 'A',
        link: function ($scope, $element, $attrs, ngModelController) {
            ngModelController.$formatters.unshift(function (value) {
                if (ngModelController.$invalid && angular.isUndefined(value)) {
                    return ngModelController.$modelValue;
                } else {
                    return value;
                }
            });
            ngModelController.$parsers.push(function (value) {
                if (ngModelController.$invalid && angular.isUndefined(value)) {
                    return ngModelController.$viewValue;
                } else {
                    return value;
                }
            });
        }
    };
});
/* Styles go here */

form {
    padding: 10px;
    border: 1px solid black;
}
input.ng-invalid {
    border: 1px solid red;
}