10
votes

I have a validation directive called valid-number that is used to set the validity of a form using $setValidity - this works fine for any text values that I type into the input box that have the directive applied to as an attribute.

The HTML is

<form name="numberForm">
<input name="amount" type="text" ng-model="amount" required  valid-number /></form>

The directive is as follow

angular.module('test',[]).directive('validNumber',function(){
            return{
                require: "ngModel",
                link: function(scope, elm, attrs, ctrl){

                    var regex=/\d/;
                    ctrl.$parsers.unshift(function(viewValue){
                        var floatValue = parseFloat(viewValue);

                        if(regex.test(viewValue)){
                            ctrl.$setValidity('validNumber',true);
                        }
                        else{
                            ctrl.$setValidity('validNumber',false);
                        }
                        return viewValue;
                    });
                }
            };
        });

However, I would also like the validation to be triggered and set the css to an invalid clsss if the value the input box is initialised to when the page is first loaded is invalid, eg if I set $scope.amount = 'not a number' I would expect the input box to have had the directive applied to it, but no joy. In order for not a number to be highlighted as invalid I have to make a change to the contents of the input, which triggers the directive.

How can I ensure the directive applies to whatever the <input> is initialised with?

A full code example is here;

http://jsfiddle.net/JW43C/5/

3

3 Answers

16
votes

$parsers array contains a list of functions that will be applied to the value that model receives from the view (what user types in), and $formatters array contains the list of functions that are being applied to the model value before it's displayed in the view.

In your directive you correctly used the $parsers array, but you also need to add the $formatters array if you want the initial value to be validated:

angular.module('test',[]).directive('validNumber',function(){
  return{
    require: "ngModel",
    link: function(scope, elm, attrs, ctrl){
      var regex = /^\d$/;
      var validator = function(value){
        ctrl.$setValidity('validNumber', regex.test(value));
        return value;
      };

      ctrl.$parsers.unshift(validator);
      ctrl.$formatters.unshift(validator);
    }
  };
});

Demo plunker

1
votes

You can simply call your verification function during the linking phase, like in this fiddle :

link: function(scope, elm, attrs, ctrl) {                       
    var regex=/\d/;
    var verificationFunction = function(viewValue) {
        var floatValue = parseFloat(viewValue);

        if(regex.test(viewValue)) {
            ctrl.$setValidity('validNumber',true);
            return viewValue;
        }
        else {
            ctrl.$setValidity('validNumber',false);
            return undefined;
        }
    };

    ctrl.$parsers.unshift(verificationFunction);
    verificationFunction();
}
0
votes

After (>=) angular 1.3.1 version was released you could implement that behaviour with a little bit correct way, following angular validation directives style (e.g. required, maxlength).

In that case you have to append your validator as property of $validators array and there are no need in $parsers or $formatters anymore:

var app = angular.module('test', []);

app
  .directive('validNumber', function() {
    return {
      require: "ngModel",
      link: function(scope, elm, attrs, ctrl) {
        var regex = /^\d+$/;

        ctrl.$validators['validNumber'] = function(modelValue, viewValue) {
          return regex.test(viewValue);
        };
      }
    };
  });

app.controller('NumberCtrl', NumberCtrl);

function NumberCtrl($scope) {
  $scope.amount = '5z';
};
input.ng-invalid {
  background-color: #FA787E;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.min.js"></script>

<div ng-app="test">
  <div ng-controller="NumberCtrl">

    <div ng-form name="numberForm">
      <input name="amount"
             type="text"
             ng-model="amount"
             required 
             valid-number />
      
      <span ng-show="numberForm.amount.$error.validNumber">
        Doesn't look like an integer
      </span>
    </div>        
  </div>
</div>