How to allow only a number (digits and decimal point) to be typed in an input?

I am new to angularjs. I am wondering what is the way to allow only a valid number typed into a textbox. For example, user can type in "1.25", but cannot type in "1.a" or "1..". When user try to type in the next character which will make it an invalid number, he cannot type it in.

Thanks in advance.


Solution 1:

I wrote a working CodePen example to demonstrate a great way of filtering numeric user input. The directive currently only allows positive integers, but the regex can easily be updated to support any desired numeric format.

My directive is easy to use:

<input type="text" ng-model="employee.age" valid-number />

The directive is very easy to understand:

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

app.controller('MainCtrl', function($scope) {
});

app.directive('validNumber', function() {
  return {
    require: '?ngModel',
    link: function(scope, element, attrs, ngModelCtrl) {
      if(!ngModelCtrl) {
        return; 
      }

      ngModelCtrl.$parsers.push(function(val) {
        if (angular.isUndefined(val)) {
            var val = '';
        }
        var clean = val.replace( /[^0-9]+/g, '');
        if (val !== clean) {
          ngModelCtrl.$setViewValue(clean);
          ngModelCtrl.$render();
        }
        return clean;
      });

      element.bind('keypress', function(event) {
        if(event.keyCode === 32) {
          event.preventDefault();
        }
      });
    }
  };
});

I want to emphasize that keeping model references out of the directive is important.

I hope you find this helpful.

Big thanks to Sean Christe and Chris Grimes for introducing me to the ngModelController

Solution 2:

You could try this directive to stop any invalid characters from being entered into an input field. (Update: this relies on the directive having explicit knowledge of the model, which is not ideal for reusability, see below for a re-usable example)

app.directive('isNumber', function () {
    return {
        require: 'ngModel',
        link: function (scope) {    
            scope.$watch('wks.number', function(newValue,oldValue) {
                var arr = String(newValue).split("");
                if (arr.length === 0) return;
                if (arr.length === 1 && (arr[0] == '-' || arr[0] === '.' )) return;
                if (arr.length === 2 && newValue === '-.') return;
                if (isNaN(newValue)) {
                    scope.wks.number = oldValue;
                }
            });
        }
    };
});

It also accounts for these scenarios:

  1. Going from a non-empty valid string to an empty string
  2. Negative values
  3. Negative decimal values

I have created a jsFiddle here so you can see how it works.

UPDATE

Following Adam Thomas' feedback regarding not including model references directly inside a directive (which I also believe is the best approach) I have updated my jsFiddle to provide a method which does not rely on this.

The directive makes use of bi-directional binding of local scope to parent scope. The changes made to variables inside the directive will be reflected in the parent scope, and vice versa.

HTML:

<form ng-app="myapp" name="myform" novalidate> 
    <div ng-controller="Ctrl">
        <number-only-input input-value="wks.number" input-name="wks.name"/>
    </div>
</form>

Angular code:

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

app.controller('Ctrl', function($scope) {
    $scope.wks =  {number: 1, name: 'testing'};
});
app.directive('numberOnlyInput', function () {
    return {
        restrict: 'EA',
        template: '<input name="{{inputName}}" ng-model="inputValue" />',
        scope: {
            inputValue: '=',
            inputName: '='
        },
        link: function (scope) {
            scope.$watch('inputValue', function(newValue,oldValue) {
                var arr = String(newValue).split("");
                if (arr.length === 0) return;
                if (arr.length === 1 && (arr[0] == '-' || arr[0] === '.' )) return;
                if (arr.length === 2 && newValue === '-.') return;
                if (isNaN(newValue)) {
                    scope.inputValue = oldValue;
                }
            });
        }
    };
});