I have my form validations dynamically added to the form from a response to a web service call. When the call returns it tells my validation directive what validations I need to add to the form. (I do this because I want to reuse the same validations on the server during submit as I do on the client.) This works wonderfully when the only validations are of type "required". The problem I have is when the model value of the form control does not pass the the validation rules, the model value is then "undefined". Therefore nothing get's sent to the server on form submission to validate on the server side. I do realize I could block the form submission if the form is not valid, however, I am letting the server determine the validity of the data that comes across.
What could I do to force the model value to be the "invalid value" regardless if it violated a validation rule? Better suggestions? Below is a snipit of my directive I am using.
//this directive should be put on an ng-form element and it will hide/show any validations set on each input
.directive('formValidator', ['validatorService', function (vs) {
return {
restrict: 'A',
require: '^form',
link: function (scope, element, attrs, form) {
function iterateOverErrorsObject(errors, func, ignorechecking) {
if (!func)
return;
//show any new errors
for (var key in errors) {
if (key.indexOf('__') == 0)
continue;
_.each(errors[key], function (obj) {
if (form[obj.$name] == obj || ignorechecking) { //ensure the obj is for the current form
var input = vs.findElementByName(element, obj.$name);
if (input.length > 0) {
func(input, obj);
}
}
});
}
}
scope.$watch(function () { return form.$error; }, function (newval, oldval, scp) {
iterateOverErrorsObject(oldval, function (input, obj) {
vs.hideErrors(input);
}, true);
iterateOverErrorsObject(newval, function (input, obj) {
vs.showErrors(input, obj, form._attr);
});
}, true);
//something told the validator to show it's errors
scope.$on('show-errors', function (evt) {
iterateOverErrorsObject(form.$error, function (input, obj) {
vs.showErrors(input, obj, form._attr);
});
});
scope.$on('hide-errors', function (evt) {
vs.hideAllErrors(form);
});
}
};
}])
//this directive is to be put on the ng-form element and will dynamically add/remove validators based on the validations configuration
//which comes back from the service call "Validate"
.directive('dynamicValidators', ['$compile', function ($compile) {
return {
priority: 0,
restrict: 'A',
//require: 'ngModel',
require: '^form',
scope: {
'validations': '=',
},
link: function (scope, element, attrs, ctrl) {
(function (form, scp) {
// this will hold any information necessary to get the error message displayed
// **have to add the form because the ctrl gets recreated every time the form.$error changes
function setAttr(ctrl, key, value) {
if (!ctrl._attr)
ctrl._attr = {};
if (!form._attr)
from._attr = {};
ctrl._attr[key] = value;
var obj = form._attr[ctrl.$name] = {};
obj[key] = value;
};
scope.$watch('validations', function (nv, ov) {
form._attr = {};
//remove old validators
if (ov && ov.length > 0) {
_.each(ov, function (e) {
var fctrl = form[e.MemberNames[0]];
if (fctrl && fctrl.$validators) {
delete fctrl.$validators[e.ErrorKey];
//fctrl.$setValidity(e.ErrorKey, true);
fctrl.$validate();
}
});
}
//add new validators
if (nv && nv.length > 0) {
_.each(nv, function (e) {
var fctrl = form[e.MemberNames[0]];
if (!fctrl)
return;
if (e.ErrorKey == 'required') {
setAttr(fctrl, e.ErrorKey, e.ErrorValue);
fctrl.$validators[e.ErrorKey] = function (modelValue, viewValue) {
if (modelValue instanceof Array)
return modelValue.length > 0;
else
return modelValue !== '' && modelValue !== null && modelValue !== undefined;
};
} else if (e.ErrorKey == 'alphanumeric') {
setAttr(fctrl, e.ErrorKey, e.ErrorValue);
fctrl.$validators[e.ErrorKey] = function (modelValue, viewValue) {
return viewValue == null || (viewValue != null && /^[a-zA-Z0-9]*$/.test(modelValue));
};
} else if (e.ErrorKey == 'min') {
setAttr(fctrl, e.ErrorKey, e.ErrorValue);
fctrl.$validators[e.ErrorKey] = function (modelValue, viewValue) {
return modelValue === undefined || modelValue === null || modelValue === "" || modelValue >= e.ErrorValue;
}
} else if (e.ErrorKey == 'max') {
setAttr(fctrl, e.ErrorKey, e.ErrorValue);
fctrl.$validators[e.ErrorKey] = function (modelValue, viewValue) {
return modelValue === undefined || modelValue === null || modelValue === "" || modelValue <= e.ErrorValue;
}
}
//make the validator fire to set the status of the validator
if (fctrl.$validators[e.ErrorKey])
//fctrl.$setValidity(e.ErrorKey, fctrl.$validators[e.ErrorKey](fctrl.$modelValue, fctrl.$viewValue))
fctrl.$validate();
});
}
});
})(ctrl, scope);
},
}
}]);
If you still want to send to the server invalid data, you can use the allowInvalid option with the ngModelOptions directive:
<input type="text" name="userName"
ng-model="user.name"
ng-model-options="{ allowInvalid: true }" />
From the documentation for ngModelOptions:
Model updates and validation
The default behaviour in ngModel is that the model value is set to
undefined when the validation determines that the value is invalid. By
setting the allowInvalid property to true, the model will still be
updated even if the value is invalid.
I'm making a validator which validates valid dates like MM/YYYY, but I didn't get how to access an attribute when the model changes:
<input id="my-date"
validate-short-date
data-max-date="{{thisMonth}}"
type="text"
name="myDate"
data-ng-model="myModelDate">
Here is the directive
.directive('validateShortDate', ['moment', function(moment) {
return {
restrict: 'A',
require: 'ngModel',
link: function($scope, element, attr, ngModel) {
var maxDate = false;
var pattern, regex;
pattern = '^((0[0-9])|(1[0-2])|[1-9])\/(19|20)[0-9]{2}$';
regex = new RegExp(pattern, 'i');
if(!angular.isUndefined(attr.maxDate)) {
// GOT ONLY ONCE
maxDate = attr.maxDate;
}
ngModel.$validators.maxDate = function(modelValue) {
// maxDate var is undefined after the first time
if (maxDate && regex.test(modelValue)) {
var modelDate = moment(modelValue, 'MM/YYYY').format('YYYYMM');
return modelDate <= maxDate;
}
return true;
};
ngModel.$validators.valid = function(modelValue) {
return modelValue === '' || modelValue === null || angular.isUndefined(modelValue) || regex.test(modelValue);
};
}
};
}])
The validator ngModel.$validators.valid works perfect, but inside ngModel.$validators.maxDate i cannot get the attr.maxDate but the first time directive fires.
So how can I access to a custom attribute value every time I check the modelValue?
I'm not an expert with AngularJS and probably I'm missing something important.
The attrs argument in the link function provides you with a $observe method which you can use to attach a listener function for dynamic changes in an attribute value.
It is very simple to use inside of your link function:
attr.$observe('maxDate', function() {
scope.maxDate = attr.maxDate;
ngModel.$validate();
});
Here is a working Plunker
You can do like this for track the change in ng-model:-
HTML
<input id="my-date"
validate-short-date
data-max-date="{{thisMonth}}"
type="text"
name="myDate"
data-ng-model="myModelDate">
Angularjs code:-
app.directive('validateShortDate', ['moment', function(moment) {
return {
restrict: 'A',
require: 'ngModel',
link: function($scope, element, attr, ngModel) {
var maxDate = false;
var pattern, regex;
pattern = '^((0[0-9])|(1[0-2])|[1-9])\/(19|20)[0-9]{2}$';
regex = new RegExp(pattern, 'i');
if(!angular.isUndefined(attr.maxDate)) {
// GOT ONLY ONCE
maxDate = attr.maxDate;
}
ngModel.$validators.maxDate = function(modelValue) {
// maxDate var is undefined after the first time
if (maxDate && regex.test(modelValue)) {
var modelDate = moment(modelValue, 'MM/YYYY').format('YYYYMM');
return modelDate <= maxDate;
}
return true;
};
ngModel.$validators.valid = function(modelValue) {
return modelValue === '' || modelValue === null || angular.isUndefined(modelValue) || regex.test(modelValue);
};
}
$scope.$watch('ngModel',function(){
console.log(attr.dataMaxDate);
});
};
}])
i have requirement where min value of one field depends on the input given in another field.
<input type="number" name="minval" class="form-control" ng-model="user.minval"
ng-required="true">
this input is used to validate another field
<input type="number" name="inputval" class="form-control" ng-model="user.inputval"
ng-required="true" min="{{user.minval}}">
but this is not working as expected.. if i change the "minval" later the input does not get revalidated..
i have tried setting the initial value for min from JS as was suggested in some solution but thats also not helping...
PLUNKER LINK
use ng-min/ng-max directives
app.directive('ngMin', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attr, ctrl) {
scope.$watch(attr.ngMin, function(){
if (ctrl.$isDirty) ctrl.$setViewValue(ctrl.$viewValue);
});
var isEmpty = function (value) {
return angular.isUndefined(value) || value === "" || value === null;
}
var minValidator = function(value) {
var min = scope.$eval(attr.ngMin) || 0;
if (!isEmpty(value) && value < min) {
ctrl.$setValidity('ngMin', false);
return undefined;
} else {
ctrl.$setValidity('ngMin', true);
return value;
}
};
ctrl.$parsers.push(minValidator);
ctrl.$formatters.push(minValidator);
}
};
});
app.directive('ngMax', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attr, ctrl) {
scope.$watch(attr.ngMax, function(){
if (ctrl.$isDirty) ctrl.$setViewValue(ctrl.$viewValue);
});
var maxValidator = function(value) {
var max = scope.$eval(attr.ngMax) || Infinity;
if (!isEmpty(value) && value > max) {
ctrl.$setValidity('ngMax', false);
return undefined;
} else {
ctrl.$setValidity('ngMax', true);
return value;
}
};
ctrl.$parsers.push(maxValidator);
ctrl.$formatters.push(maxValidator);
}
};
});
I've developed a couple of directives that actually restrict the user from setting an invalid value instead of simply throwing an error when an invalid value is provided.
These directives also do not require ngModel (though I doubt you would use them without) and what's really cool is that it will wrap the value around to the min/max if both settings are provided!
I've tried to simplify the directives as much as possible to make them easier for our readers.
Here is a JSFiddle of the whole thing: JSFiddle
And here are the directives:
app.directive('ngMin', function($parse){
return {
restrict: 'A',
link: function(scope, element, attrs){
function validate(){
if(element
&& element[0]
&& element[0].localName === 'input'
&& isNumber(attrs.ngMin)
&& isNumber(element[0].value)
&& parseFloat(element[0].value) < parseFloat(attrs.ngMin)){
if(isNumber(attrs.ngMax)){
element[0].value = parseFloat(attrs.ngMax);
if(attrs.hasOwnProperty("ngModel"))
$parse(attrs.ngModel).assign(scope, parseFloat(attrs.ngMax));
}
else {
element[0].value = parseFloat(attrs.ngMin);
if(attrs.hasOwnProperty("ngModel"))
$parse(attrs.ngModel).assign(scope, parseFloat(attrs.ngMin));
}
}
}
scope.$watch(function(){
return attrs.ngMin + "-" + element[0].value;
}, function(newVal, oldVal){
if(newVal != oldVal)
validate();
});
validate();
}
};
});
app.directive('ngMax', function($parse){
return {
restrict: 'A',
link: function(scope, element, attrs){
function validate(){
if(element
&& element[0]
&& element[0].localName === 'input'
&& isNumber(attrs.ngMax)
&& isNumber(element[0].value)
&& parseFloat(element[0].value) > parseFloat(attrs.ngMax)){
if(isNumber(attrs.ngMin)){
element[0].value = parseFloat(attrs.ngMin);
if(attrs.hasOwnProperty("ngModel"))
$parse(attrs.ngModel).assign(scope, parseFloat(attrs.ngMin));
}
else {
element[0].value = parseFloat(attrs.ngMax);
if(attrs.hasOwnProperty("ngModel"))
$parse(attrs.ngModel).assign(scope, parseFloat(attrs.ngMax));
}
}
}
scope.$watch(function(){
return attrs.ngMax + "-" + element[0].value;
}, function(newVal, oldVal){
if(newVal != oldVal)
validate();
});
validate();
}
};
});
...also, you will need this little helper function as well:
function isNumber(n){
return !isNaN(parseFloat(n)) && isFinite(n);
}
To invoke these directives, just set them on an input box where type="number":
<input ng-model="myModel" ng-min="0" ng-max="1024" />
And that should do it!
When you provide both an ngMin and ngMax, these directive will wrap the value around, so that when your value becomes less than ngMin, it will be set to ngMax, and vice-versa.
If you only provide ngMin or ngMax, the input value will simply be capped at these values.
I prefer this method of preventing bad values rather than alerting the user that they have entered a bad value.
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.
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
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:
Going from a non-empty valid string to an empty string
Negative values
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;
}
});
}
};
});
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();
}
});
}
};
});
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/
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
You could easily use the ng-pattern.
ng-pattern="/^[1-9][0-9]{0,2}(?:,?[0-9]{3}){0,3}(?:\.[0-9]{1,2})?$/"
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
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);
}
};
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;
});
}
};
});
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.
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;
});
}
};
});
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');
});
}
};
});
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
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;
}
}
});
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;
});
}
};
});
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
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.
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