59

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, they cannot type it in.

1
  • 2
    You could use ng-change to call a function that checks the input. Commented Sep 26, 2013 at 19:09

18 Answers 18

104

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

Sign up to request clarification or add additional context in comments.

14 Comments

Good solution. I agree it is important to decouple and not directly reference the model in your directives. However, your example does not cover when someone wants to to type -1.
How can I make this work with input type="number"?
Additionally how can this be updated to allow for - and .?
Love it Adam. But to @bbodenmiller 's point, why does this only work with type="text" it breaks on type="number" :( Love it either way!
Thank you - work great. I think you need to remove the "var" from this line: if (angular.isUndefined(val)) { var val = ''; }
|
52

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;
                }
            });
        }
    };
});

19 Comments

The jsFiddle appears to be pointing to a different example. You'll need to check for a "-" at the start of the value so that it'll accept a negative number: (value === "-")
@AdamThomas I have updated my answer following your feedback. Thank you for your thoughts.
Thanks @GordyD : I updated your code to make an input-price who allow two decimals maximum and transform , to . | jsfiddle : jsfiddle.net/vfsHX/233
The solution gives error when input dot and then comma. This fiddle jsfiddle.net/34ojnnfk/1 is an alternative as it uses attribute decimal-number to indicate that only numbers are accepted. It works on angular 1.2.1 and up.
The code has a problem when number is undefined so it force you to put always a value.
|
45

First of all Big thanks to Adam thomas I used the same Adam's logic for this with a small modification to accept the decimal values.

Note: This will allow digits with only 2 decimal values

Here is my Working Example

HTML

<input type="text" ng-model="salary" valid-number />

Javascript

    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, '');
            var decimalCheck = clean.split('.');

            if(!angular.isUndefined(decimalCheck[1])) {
                decimalCheck[1] = decimalCheck[1].slice(0,2);
                clean =decimalCheck[0] + '.' + decimalCheck[1];
            }

            if (val !== clean) {
              ngModelCtrl.$setViewValue(clean);
              ngModelCtrl.$render();
            }
            return clean;
          });

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

2 Comments

You should convert val to string before val.replace because it spits error val.replace is not function when input[type=number].
Thanks, this is working exactly like I want. However I wanted flexible amount of decimals, so I pass it as an attribute like so decimalCheck[1] = decimalCheck[1].slice(0, attrs.validNumber); this was replacing the ...slice(0, 2)...
10

Use the step tag to set the minimum changeable value to some decimal number:

e.g. step="0.01"

<input type="number" step="0.01" min="0" class="form-control" 
name="form_name" id="your_id" placeholder="Please Input a decimal number" required>

There is some documentation on it here:

http://blog.isotoma.com/2012/03/html5-input-typenumber-and-decimalsfloats-in-chrome/

2 Comments

Great! I was using ng-pattern with a regex before. This one validates better. Thanks.
unable to bind it with angular is I include step="0.01"
7

DEMO - - jsFiddle

Directive

   .directive('onlyNum', function() {
      return function(scope, element, attrs) {

         var keyCode = [8,9,37,39,48,49,50,51,52,53,54,55,56,57,96,97,98,99,100,101,102,103,104,105,110];
          element.bind("keydown", function(event) {
            console.log($.inArray(event.which,keyCode));
            if($.inArray(event.which,keyCode) == -1) {
                scope.$apply(function(){
                    scope.$eval(attrs.onlyNum);
                    event.preventDefault();
                });
                event.preventDefault();
            }

        });
     };
  });

HTML

 <input type="number" only-num>

Note : Do not forget include jQuery with angular js

5 Comments

@penner - do you know how to add special characters to the list? I've tried adding 33, 34, 35 and so forth, but I'm not having anyluck
@Davis Simply adding the keycode to the whitelist array works for me. Change line 6 to console.log(event.which); to get the right keycode.
@penner - thanks for the reply. Oddly enough, it's not working.
Figured it out 'element.on('keypress keydown', function (event)'
Modded: .directive('validNumber', function() { return function(scope, element, attrs) { var keyCode = [8,9,37,39,48,49,50,51,52,53,54,55,56,57,96,97,98,99,100,101,102,103,104,105,110, 190]; var dots_used = 0; element.bind("keydown", function(event) { if(event.which == 190) ++dots_used; if(keyCode.indexOf(event.which) == -1 || (event.which == 190 && dots_used > 1)) { scope.$apply(function(){ scope.$eval(attrs.onlyNum); event.preventDefault(); }); event.preventDefault(); } }); };
7

You could easily use the ng-pattern.

ng-pattern="/^[1-9][0-9]{0,2}(?:,?[0-9]{3}){0,3}(?:\.[0-9]{1,2})?$/"

Comments

5

There is an input number directive which I belive can do just what you want.

<input type="number"
   ng-model="{string}"
   [name="{string}"]
   [min="{string}"]
   [max="{string}"]
   [required]
   [ng-required="{string}"]
   [ng-minlength="{number}"]
   [ng-maxlength="{number}"]
   [ng-pattern="{string}"]
   [ng-change="{string}"]>

the official doc is here: http://docs.angularjs.org/api/ng.directive:input.number

2 Comments

Just want to add that by default, all modern browsers will render it with inc/dec buttons. but they can be safely removed with simple css.
The question asks how to prevent typing in non-numeric text. input[type="number"] does not prevent non-numeric text from being typed in, it only sets the field as invalid if non-numeric text is entered.
4

HTML

 <input type="text" name="number" only-digits>

// Just type 123

  .directive('onlyDigits', function () {
    return {
      require: 'ngModel',
      restrict: 'A',
      link: function (scope, element, attr, ctrl) {
        function inputValue(val) {
          if (val) {
            var digits = val.replace(/[^0-9]/g, '');

            if (digits !== val) {
              ctrl.$setViewValue(digits);
              ctrl.$render();
            }
            return parseInt(digits,10);
          }
          return undefined;
        }            
        ctrl.$parsers.push(inputValue);
      }
    };

// type: 123 or 123.45

 .directive('onlyDigits', function () {
    return {
      require: 'ngModel',
      restrict: 'A',
      link: function (scope, element, attr, ctrl) {
        function inputValue(val) {
          if (val) {
            var digits = val.replace(/[^0-9.]/g, '');

            if (digits !== val) {
              ctrl.$setViewValue(digits);
              ctrl.$render();
            }
            return parseFloat(digits);
          }
          return undefined;
        }            
        ctrl.$parsers.push(inputValue);
      }
    };

1 Comment

I can put 123.444.555.666 :-(
2

I wanted a directive that could be limited in range by min and max attributes like so:

<input type="text" integer min="1" max="10" />

so I wrote the following:

.directive('integer', function() {
    return {
        restrict: 'A',
        require: '?ngModel',
        link: function(scope, elem, attr, ngModel) {
            if (!ngModel)
                return;

            function isValid(val) {
                if (val === "")
                    return true;

                var asInt = parseInt(val, 10);
                if (asInt === NaN || asInt.toString() !== val) {
                    return false;
                }

                var min = parseInt(attr.min);
                if (min !== NaN && asInt < min) {
                    return false;
                }

                var max = parseInt(attr.max);
                if (max !== NaN && max < asInt) {
                    return false;
                }

                return true;
            }

            var prev = scope.$eval(attr.ngModel);
            ngModel.$parsers.push(function (val) {
                // short-circuit infinite loop
                if (val === prev)
                    return val;

                if (!isValid(val)) {
                    ngModel.$setViewValue(prev);
                    ngModel.$render();
                    return prev;
                }

                prev = val;
                return val;
            });
        }
    };
});

1 Comment

To make it perfect you need to add ngModel.$commitViewValue(); between the $setViewValue(clean); and $render();
1

Here's my really quick-n-dirty one:

<!-- HTML file -->
<html ng-app="num">
  <head></head>
  <body ng-controller="numCtrl">
    <form class="digits" name="digits" ng-submit="getGrades()" novalidate >
      <input type="text" placeholder="digits here plz" name="nums" ng-model="nums" required ng-pattern="/^(\d)+$/" />
      <p class="alert" ng-show="digits.nums.$error.pattern">Numbers only, please.</p> 
      <br>
      <input type="text" placeholder="txt here plz" name="alpha" ng-model="alpha" required ng-pattern="/^(\D)+$/" />
      <p class="alert" ng-show="digits.alpha.$error.pattern">Text only, please.</p>
      <br>
      <input class="btn" type="submit" value="Do it!" ng-disabled="!digits.$valid" />
    </form>
  </body>
</html>

// Javascript file
var app = angular.module('num', ['ngResource']);
app.controller('numCtrl', function($scope, $http){
  $scope.digits = {};
});

This requires you include the angular-resource library for persistent bindings to the fields for validation purposes.

Working example here

Works like a champ in 1.2.0-rc.3+. Modify the regex and you should be all set. Perhaps something like /^(\d|\.)+$/ ? As always, validate server-side when you're done.

3 Comments

do you have a plurk for this? I can't seem to get it to work. is the capturing ()s required?
I apologize, my answer was a bit hasty and incomplete. It's been updated and tested with an example to function.
still doesn't work for me.i don't think angular-resource.js has anything to do with ng-pattern -- it looks like an $http library
1

This one seems the easiest to me: http://jsfiddle.net/thomporter/DwKZh/

(Code is not mine, I accidentally stumbled upon it)

    angular.module('myApp', []).directive('numbersOnly', function(){
       return {
         require: 'ngModel',
         link: function(scope, element, attrs, modelCtrl) {
           modelCtrl.$parsers.push(function (inputValue) {
               // this next if is necessary for when using ng-required on your input. 
               // In such cases, when a letter is typed first, this parser will be called
               // again, and the 2nd time, the value will be undefined
               if (inputValue == undefined) return '' 
               var transformedInput = inputValue.replace(/[^0-9]/g, ''); 
               if (transformedInput!=inputValue) {
                  modelCtrl.$setViewValue(transformedInput);
                  modelCtrl.$render();
               }         

               return transformedInput;         
           });
         }
       };
    });

1 Comment

To make it perfect you need to add modelCtrl.$commitViewValue(); between the $setViewValue(clean); and $render();
1

I modified Alan's answer above to restrict the number to the specified min/max. If you enter a number outside the range, it will set the min or max value after 1500ms. If you clear the field completely, it will not set anything.

HTML:

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

Javascript:

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

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

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

            var min = +attrs.min;
            var max = +attrs.max;
            var lastValue = null;
            var lastTimeout = null;
            var delay = 1500;

            ngModelCtrl.$parsers.push(function(val) {
                if (angular.isUndefined(val)) {
                    val = '';
                }            

                if (lastTimeout) {
                    $timeout.cancel(lastTimeout);
                }

                if (!lastValue) {
                    lastValue = ngModelCtrl.$modelValue;
                }

                if (val.length) {
                    var value = +val;
                    var cleaned = val.replace( /[^0-9]+/g, ''); 

                    // This has no non-numeric characters
                    if (val.length === cleaned.length) {
                        var clean = +cleaned;

                        if (clean < min) {
                            clean = min;
                        } else if (clean > max) {
                            clean = max;
                        }

                        if (value !== clean || value !== lastValue) {
                            lastTimeout = $timeout(function () {
                                lastValue = clean;
                                ngModelCtrl.$setViewValue(clean);
                                ngModelCtrl.$render();
                            }, delay);
                        }
                    // This has non-numeric characters, filter them out
                    } else {
                        ngModelCtrl.$setViewValue(lastValue);
                        ngModelCtrl.$render();
                    }
                }

                return lastValue;
            });

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

            element.on('$destroy', function () {
                element.unbind('keypress');
            });
        }
    };
});

Comments

1

I had a similar problem and update the input[type="number"] example on angular docs for works with decimals precision and I'm using this approach to solve it.

PS: A quick reminder is that the browsers supports the characters 'e' and 'E' in the input[type="number"], because that the keypress event is required.

angular.module('numfmt-error-module', [])
.directive('numbersOnly', function() {
  return {
    require: 'ngModel',
    scope: {
      precision: '@'
    },
    link: function(scope, element, attrs, modelCtrl) {
      var currencyDigitPrecision = scope.precision;

      var currencyDigitLengthIsInvalid = function(inputValue) {
        return countDecimalLength(inputValue) > currencyDigitPrecision;
      };

      var parseNumber = function(inputValue) {
        if (!inputValue) return null;
        inputValue.toString().match(/-?(\d+|\d+.\d+|.\d+)([eE][-+]?\d+)?/g).join('');
        var precisionNumber = Math.round(inputValue.toString() * 100) % 100;

        if (!!currencyDigitPrecision && currencyDigitLengthIsInvalid(inputValue)) {
          inputValue = inputValue.toFixed(currencyDigitPrecision);
          modelCtrl.$viewValue = inputValue;
        }
        return inputValue;
      };

      var countDecimalLength = function (number) { 
         var str = '' + number;
         var index = str.indexOf('.');
         if (index >= 0) {
           return str.length - index - 1;
         } else {
           return 0;
         }
      };

      element.on('keypress', function(evt) {
        var charCode, isACommaEventKeycode, isADotEventKeycode, isANumberEventKeycode;
        charCode = String.fromCharCode(evt.which || event.keyCode);
        isANumberEventKeycode = '0123456789'.indexOf(charCode) !== -1;
        isACommaEventKeycode = charCode === ',';
        isADotEventKeycode = charCode === '.';

        var forceRenderComponent = false;

        if (modelCtrl.$viewValue != null && !!currencyDigitPrecision) {
          forceRenderComponent = currencyDigitLengthIsInvalid(modelCtrl.$viewValue);
        }

        var isAnAcceptedCase = isANumberEventKeycode || isACommaEventKeycode || isADotEventKeycode;

        if (!isAnAcceptedCase) {
          evt.preventDefault();
        }

        if (forceRenderComponent) {
          modelCtrl.$render(modelCtrl.$viewValue);
        }

        return isAnAcceptedCase;
      });

      modelCtrl.$render = function(inputValue) {
        return element.val(parseNumber(inputValue));
      };

      modelCtrl.$parsers.push(function(inputValue) {

        if (!inputValue) {
          return inputValue;
        }

        var transformedInput;
        modelCtrl.$setValidity('number', true);
        transformedInput = parseNumber(inputValue);

        if (transformedInput !== inputValue) {

          modelCtrl.$viewValue = transformedInput;
          modelCtrl.$commitViewValue();
          modelCtrl.$render(transformedInput);
        }
        return transformedInput;
      });
    }
  };
});

And in your html you can use this approach

<input 
  type="number" 
  numbers-only 
  precision="2" 
  ng-model="model.value" 
  step="0.10" />

Here is the plunker with this snippet

4 Comments

Did you test this? It does not working correctly. When i add decimals it adds them incorrectly.
Not working very well...when I try to delete numbers it doesn't allow it
@azuax I updated the snippet. Please check it again and if you guys need something, please ping me
@CodeArtist I updated the snippet. Please check it again and if you guys need something, please ping me
0

Expanding from gordy's answer:

Good job btw. But it also allowed + in the front. This will remove it.

    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;
        }
        if (arr.length > 0) {
            if (arr[0] === "+") {
                scope.inputValue = oldValue;
            }
        }
    });

Comments

0

Here is a derivative that will also block the decimal point to be entered twice

HTML

    <input tabindex="1" type="text" placeholder="" name="salary" id="salary" data-ng-model="salary" numbers-only="numbers-only" required="required">

Angular

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

    app.directive('numbersOnly', function() {
    return {
            require : 'ngModel', link : function(scope, element, attrs,  modelCtrl) {
        modelCtrl.$parsers.push(function(inputValue) {

            if (inputValue == undefined) {
                return ''; //If value is required
            }

            // Regular expression for everything but [.] and [1 - 10] (Replace all)
            var transformedInput = inputValue.replace(/[a-z!@#$%^&*()_+\-=\[\]{};':"\\|,<>\/?]/g, '');

            // Now to prevent duplicates of decimal point
            var arr = transformedInput.split('');

            count = 0; //decimal counter
            for ( var i = 0; i < arr.length; i++) {
                if (arr[i] == '.') {
                    count++; //  how many do we have? increment
                }
            }

            // if we have more than 1 decimal point, delete and leave only one at the end
            while (count > 1) {
                for ( var i = 0; i < arr.length; i++) {
                    if (arr[i] == '.') {
                        arr[i] = '';
                        count = 0;
                        break;
                    }
                }
            }

            // convert the array back to string by relacing the commas
            transformedInput = arr.toString().replace(/,/g, '');

            if (transformedInput != inputValue) {
                modelCtrl.$setViewValue(transformedInput);
                modelCtrl.$render();
            }

            return transformedInput;
        });
    }
};
});

Comments

0

Extending Adam Thomas answer you can easily make this directive more generic by adding input argument with custom regexp:

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

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

app.directive('validInput', function() {
  return {
    require: '?ngModel',
    scope: {
      "inputPattern": '@'
    },
    link: function(scope, element, attrs, ngModelCtrl) {

      var regexp = null;

      if (scope.inputPattern !== undefined) {
        regexp = new RegExp(scope.inputPattern, "g");
      }

      if(!ngModelCtrl) {
        return;
      }

      ngModelCtrl.$parsers.push(function(val) {
        if (regexp) {
          var clean = val.replace(regexp, '');
          if (val !== clean) {
            ngModelCtrl.$setViewValue(clean);
            ngModelCtrl.$render();
          }
          return clean;
        }
        else {
          return val;
        }

      });

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

HTML

<input type="text" ng-model="employee.age" valid-input  
       input-pattern="[^0-9]+" placeholder="Enter an age" />
</label>

Live on CodePen

Comments

0

Please check out my component that will help you to allow only a particular data type. Currently supporting integer, decimal, string and time(HH:MM).

  • string - String is allowed with optional max length
  • integer - Integer only allowed with optional max value
  • decimal - Decimal only allowed with optional decimal points and max value (by default 2 decimal points)
  • time - 24 hr Time format(HH:MM) only allowed

https://github.com/ksnimmy/txDataType

Hope that helps.

1 Comment

Please let me know what difficulty you are facing for this?
0

DECIMAL

directive('decimal', function() {
                return {
                    require: 'ngModel',
                    restrict: 'A',
                    link: function(scope, element, attr, ctrl) {
                        function inputValue(val) {
                            if (val) {
                                var digits = val.replace(/[^0-9.]/g, '');

                                if (digits.split('.').length > 2) {
                                    digits = digits.substring(0, digits.length - 1);
                                }

                                if (digits !== val) {
                                    ctrl.$setViewValue(digits);
                                    ctrl.$render();
                                }
                                return parseFloat(digits);
                            }
                            return "";
                        }
                        ctrl.$parsers.push(inputValue);
                    }
                };
            });

DIGITS

directive('entero', function() {
            return {
                require: 'ngModel',
                restrict: 'A',
                link: function(scope, element, attr, ctrl) {
                    function inputValue(val) {
                        if (val) {
                            var value = val + ''; //convert to string
                            var digits = value.replace(/[^0-9]/g, '');

                            if (digits !== value) {
                                ctrl.$setViewValue(digits);
                                ctrl.$render();
                            }
                            return parseInt(digits);
                        }
                        return "";
                    }
                    ctrl.$parsers.push(inputValue);
                }
            };
        });

angular directives for validate numbers

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.