Angular model.$viewValue not showing in input field - angularjs

I previously write this [question][1], Inow I have the problem that the model.$viewValue it's not the same of the value the i see in the input box.
<div amount-input-currency="" ng-model="data.amount" ></div>
This is my directive (isNumeric and similar is not important that works weel):
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.data = { amount: ''};
});
app.directive('amountInputCurrency', function () {
return {
restrict: 'EA',
require: 'ngModel',
templateUrl: 'inputCurrency.tmpl.html',
scope: {
model: '=ngModel',
},
link: function (scope, elem, attrs, ngModelCtrl) {
scope.model2 = ngModelCtrl;
console.log("I am in the directive!");
var myAmountCurrencyType = elem.find('.cb-amount-input-currency');
scope.onFocus = function() {
removeThousandSeparator();
};
scope.onBlur = function() {
renderValue();
ngModelCtrl.$render();
};
//format text going to user (model to view)
ngModelCtrl.$formatters.push(function(value) {
return parseValue(value);
});
//format text from the user (view to model)
ngModelCtrl.$parsers.push(function(value) {
var num = Number(value);
if(isNumeric(num)) {
var decimal = 2;
return formatAmount();
} else {
return value;
}
});
function isNumeric(val) {
return Number(parseFloat(val))==val;
}
}
}
});
And this is my template:
scope.model: {{model}}<br>
viewValue: {{model2.$viewValue}}<br>
modelValue: {{model2.$modelValue}}<br>
<input type="text" class="amount-input-currency form-control" x-ng-model="model" ng-focus="onFocus()" ng-blur="onBlur()"></input>

Set the viewValue using ngModelCtrl.$setViewValue() in order to update the model instead of setting $viewValue field directly. But I am not sure what is the point of using NgModelController in this case at all.
If the only purpose is to format the value of the textbox, manipulate the input element value instead of NgModelController fields.
function renderValue() {
var myAmountCurrencyType = elem.find('input');
var value = myAmountCurrencyType.val();
var decimal = 2;
if (value != undefined && value !="") {
myAmountCurrencyType.val(formatAmount());
}
}
This way it does not update the model. If you want to have full control over the data binding you can consider removing the binding from the input element x-ng-model="model" and implementing it using NgModelController in your directive.

If you can do what you need to do without formatters and parsers it's better because that you work more in the angular way, and if you can avoid requiring ng-model in directives if you can manage without it because that cause a lot of mess too.
As for your problem I will show a solution that does not need the formatters and parsers and I hope that this what you wanted, if you must use the formatters and parsers you can, but it will essentially do the same as the following solution:
index.html:
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js#1.2.x" src="http://code.angularjs.org/1.2.15/angular.js" data-semver="1.2.15"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<!--<div amount-input-currency="" ng-model="data.amount" ></div>-->
<amount-input-currency model="data.amount"></amount-input-currency>
</body>
</html>
amountInputCurrency.tmpl.html:
scope.model: {{model}}<br>
viewValue: {{model2.$viewValue}}<br>
modelValue: {{model2.$modelValue}}<br>
<input type="text" class="cb-amount-input-currency form-control" ng-model="model" ng-focus="onFocus()" ng-blur="onBlur()">
app.js:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.data = { amount: ''};
});
app.directive('amountInputCurrency', function () {
var isAllowedKey = function (k, v) {
return (
k === 8 || k === 9 || k === 46 ||
(k > 47 && k < 58) ||
(k > 95 && k < 106) ||
(k > 36 && k < 41) ||
(k === 188 && (!v || v.search(/(\.|\,)/)<0)) ||
((k === 190 || k === 110) && (!v || v.search(/(\.|\,)/)<0))
);
};
return {
restrict: 'E',
// require: 'ngModel',
templateUrl: 'amountInputCurrency.tmpl.html',
scope: {
model: '=',
},
link: function (scope, elem, attrs) {
// scope.model2 = ngModelCtrl;
console.log("I am in the directive!");
var myAmountCurrencyType = elem.find('.cb-amount-input-currency');
myAmountCurrencyType.on('keydown', function (e) {
//if (!isAllowedKey(e.which, scope.model)) {
if (!isAllowedKey(e.which, scope.model)) {
e.preventDefault();
}
});
scope.onFocus = function() {
removeThousandSeparator();
};
scope.onBlur = function() {
renderValue();
// ngModelCtrl.$render();
// scope.model = ngModelCtrl.$viewValue;
};
// //format text going to user (model to view)
// ngModelCtrl.$formatters.push(function(value) {
// return parseValue(value);
// });
// //format text from the user (view to model)
// ngModelCtrl.$parsers.push(function(value) {
// var num = Number(value);
// if(isNumeric(num)) {
// var decimal = 2;
// return formatAmount(Number(num).toFixed(decimal), decimal, ',', '.');
// } else {
// return value;
// }
// });
function isNumeric(val) {
return Number(parseFloat(val))==val;
}
function renderValue() {
var value = String(scope.model || '');
var decimal = attrs.cbAmountDecimal || 2;
if (value != undefined && value !="") {
scope.model = formatAmount(value, decimal, ',', '.');
// ngModelCtrl.$render();
}
}
function formatAmount(amount, c, d, t) {
if (amount.indexOf(',') !== -1) {
if (amount.indexOf('.') !== -1) {
amount = amount.replace(/\./g,''); //remove thousand separator
}
amount = amount.replace(/\,/g,'.');
}
c = isNaN(c = Math.abs(c)) ? 2 : c;
d = d === undefined ? "." : d;
t = t === undefined ? "," : t;
var n = amount,
s = n < 0 ? "-" : "",
i = parseInt(n = Math.abs(+n || 0).toFixed(c)) + "",
j = (j = i.length) > 3 ? j % 3 : 0;
return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : "");
}
function removeThousandSeparator() {
if(scope.model != undefined && scope.model !="") {
scope.model = scope.model.replace(/\./g,'');
// ngModelCtrl.$render();
// scope.model = ngModelCtrl.$viewValue;
}
}
function parseValue(viewValue) {
var num = 0;
if(isNumeric(viewValue)) {
num = viewValue;
} else {
num = viewValue ? viewValue.replace(/,/g,'.') : viewValue;
}
return num;
}
}
}
});
If this is not what you want then I'm truly sorry and please comment what is the problem in my solution for and I will try my best to see if I can help.

Checkout Formatters and Parser, it's how to do what you want, but you have to require ngModel to hook append formatter or parser.
A good article about them : https://alexperry.io/angularjs/2014/12/10/parsers-and-formatters-angular.html
Regards.

Related

about angularJS directives

I write a directive called ValidateChineseDirective.
define(function(){
'use strict';
return function(module){
module.directive('validateChinese',function(){
return {
restrict:'A',
require:'ngModel',
link:function(scope,ele,attr,ngModel){
if (!ngModel) return;
var maxlength = -1;
attr.$observe('validateChinese', function(value) {
var intVal = parseInt(value,10);
maxlength = isNaN(intVal) ? -1 : intVal;
ngModel.$validate();
});
ngModel.$parsers.push(function(viewValue){
var valueArray = viewValue.split("");
var reg = /[\u4E00-\u9FA5\uF900-\uFA2D]/;
var len=0;
for(var i=0;i<valueArray.length;i++){
len += reg.test(valueArray[i])?4:1;
}
if(len<=maxlength){
ngModel.$setValidity('validateChinese',true);
}else{
ngModel.$setValidity('validateChinese',false);
}
return viewValue;
});
}
}
})
}
})
html:
<input type="text" name="approvedDocNo" validate_chinese="4" ng-model="fundMaintenanceVM.editData.approvedDocNo" class="form-control">
the reg was used for matching chinese.
I found that whether input space at the begin of the input box or at the end of it.
ngModel.$parsers.push didn't trigger until type a character.
furthermore,when ngModel.$parsers.push triggered, viewValue didn't contain the space which is at the end of this input box or at the end of it.Does somebody can help me ,thx.
add ng-trim="false" to the input element which used your directive.
The reason is that: Angular sets ng-trim to true by default, which trims white space in input boxes, and leads no change with ngModel.
angular.module("app", []).directive('validateChinese', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, ele, attr, ngModel) {
if (!ngModel) return;
var maxlength = -1;
attr.$observe('validateChinese', function(value) {
var intVal = parseInt(value, 10);
maxlength = isNaN(intVal) ? -1 : intVal;
ngModel.$validate();
});
ngModel.$parsers.push(function(viewValue) {
var valueArray = viewValue.split("");
var reg = /[\u4E00-\u9FA5\uF900-\uFA2D]/;
var len = 0;
for (var i = 0; i < valueArray.length; i++) {
len += reg.test(valueArray[i]) ? 4 : 1;
}
if (len <= maxlength) {
ngModel.$setValidity('validateChinese', true);
} else {
ngModel.$setValidity('validateChinese', false);
}
console.log('ngModel.$parsers.push fired.');
return viewValue;
});
}
}
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.4/angular.min.js"></script>
<div ng-app="app">
<input type="text" name="approvedDocNo" validate_chinese="4" ng-model="approvedDocNo" ng-trim="false" class="form-control">
{{approvedDocNo}}
</div>

UI grid - dropdownEditor with search/filter

I'm using editable UI-Grid in my page.
One of the columns is a dropdownlist with many options as the image bellow:
I need to allow the user to search to find the item they want.
Out of the grid, i'm useing bootstrap selectpicker, as the image bellow
Please try the chosen plugin which is more suitable to your needs :
index.html
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<link data-require="chosen#*" data-semver="1.0.0" rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/chosen/1.0/chosen.min.css" />
<script data-require="jquery#*" data-semver="2.2.0" src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<script data-require="chosen#*" data-semver="1.0.0" src="//cdnjs.cloudflare.com/ajax/libs/chosen/1.0/chosen.jquery.min.js"></script>
<script data-require="chosen#*" data-semver="1.0.0" src="//cdnjs.cloudflare.com/ajax/libs/chosen/1.0/chosen.proto.min.js"></script>
<script src="https://code.angularjs.org/1.5.0/angular.min.js"></script>
<script src="chosen.js"></script>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<h1>{{name}}</h1>
<select chosen multiple id="states" style="width:500px">
<option value="AK">Alaska</option>
<option value="AZ">Arizona</option>
<option value="AR">Arkansas</option>
<option value="CA">California</option>
</select>
</body>
</html>
Your app.js
var app = angular.module('myApp', ['localytics.directives']);
app.controller('MainCtrl', function($scope) {
$scope.name = 'Angular Chosen with <option>';
});
chosen.js
// Generated by CoffeeScript 1.8.0
(function() {
var __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
angular.module('localytics.directives', []);
angular.module('localytics.directives').directive('chosen', [
'$timeout', function($timeout) {
var CHOSEN_OPTION_WHITELIST, NG_OPTIONS_REGEXP, isEmpty, snakeCase;
NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+(.*?)(?:\s+track\s+by\s+(.*?))?$/;
CHOSEN_OPTION_WHITELIST = ['noResultsText', 'allowSingleDeselect', 'disableSearchThreshold', 'disableSearch', 'enableSplitWordSearch', 'inheritSelectClasses', 'maxSelectedOptions', 'placeholderTextMultiple', 'placeholderTextSingle', 'searchContains', 'singleBackstrokeDelete', 'displayDisabledOptions', 'displaySelectedOptions', 'width'];
snakeCase = function(input) {
return input.replace(/[A-Z]/g, function($1) {
return "_" + ($1.toLowerCase());
});
};
isEmpty = function(value) {
var key;
if (angular.isArray(value)) {
return value.length === 0;
} else if (angular.isObject(value)) {
for (key in value) {
if (value.hasOwnProperty(key)) {
return false;
}
}
}
return true;
};
return {
restrict: 'A',
require: '?ngModel',
priority: 1,
link: function(scope, element, attr, ngModel) {
var chosen, defaultText, disableWithMessage, empty, initOrUpdate, match, options, origRender, removeEmptyMessage, startLoading, stopLoading, valuesExpr, viewWatch;
element.addClass('localytics-chosen');
options = scope.$eval(attr.chosen) || {};
angular.forEach(attr, function(value, key) {
if (__indexOf.call(CHOSEN_OPTION_WHITELIST, key) >= 0) {
return options[snakeCase(key)] = scope.$eval(value);
}
});
startLoading = function() {
return element.addClass('loading').attr('disabled', true).trigger('chosen:updated');
};
stopLoading = function() {
return element.removeClass('loading').attr('disabled', false).trigger('chosen:updated');
};
chosen = null;
defaultText = null;
empty = false;
initOrUpdate = function() {
if (chosen) {
return element.trigger('chosen:updated');
} else {
chosen = element.chosen(options).data('chosen');
return defaultText = chosen.default_text;
}
};
removeEmptyMessage = function() {
empty = false;
return element.attr('data-placeholder', defaultText);
};
disableWithMessage = function() {
empty = true;
return element.attr('data-placeholder', chosen.results_none_found).attr('disabled', true).trigger('chosen:updated');
};
if (ngModel) {
origRender = ngModel.$render;
ngModel.$render = function() {
origRender();
return initOrUpdate();
};
if (attr.multiple) {
viewWatch = function() {
return ngModel.$viewValue;
};
scope.$watch(viewWatch, ngModel.$render, true);
}
} else {
initOrUpdate();
}
attr.$observe('disabled', function() {
return element.trigger('chosen:updated');
});
if (attr.ngOptions && ngModel) {
match = attr.ngOptions.match(NG_OPTIONS_REGEXP);
valuesExpr = match[7];
scope.$watchCollection(valuesExpr, function(newVal, oldVal) {
var timer;
return timer = $timeout(function() {
if (angular.isUndefined(newVal)) {
return startLoading();
} else {
if (empty) {
removeEmptyMessage();
}
stopLoading();
if (isEmpty(newVal)) {
return disableWithMessage();
}
}
});
});
return scope.$on('$destroy', function(event) {
if (typeof timer !== "undefined" && timer !== null) {
return $timeout.cancel(timer);
}
});
}
}
};
}
]);
}).call(this);

How to fix size of the number before decimal point in Angularjs?

I am using the below code to fix the type of the field.
I have field named as "Marks %" which should allow only numbers and decimals that too it should allow only two digits before decimal point and two digit after decimal point.
My HTML :
<div ng-app="jmApp">
<h2>Validate Price</h2>
<div ng-controller="MainCtrl">
<input type="text" ng-model="salary" valid-number />
</div>
</div>
My Controller :
var app = angular.module('jmApp', []);
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 negativeCheck = clean.split('-');
var decimalCheck = clean.split('.');
if(!angular.isUndefined(negativeCheck[1])) {
negativeCheck[1] = negativeCheck[1].slice(0, negativeCheck[1].length);
clean =negativeCheck[0] + '-' + negativeCheck[1];
if(negativeCheck[0].length > 0) {
clean =negativeCheck[0];
}
}
if(!angular.isUndefined(decimalCheck[1])) {
decimalCheck[0] = decimalCheck[0].slice(0,2);
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();
}
});
}
};
});
OUTPUT :
It is allowing only numbers.
It will consider only two digits after decimal point.
If I entered any number of digits followed by decimal point it accepts only first two digits and decimal point.
But if I entered whole numbers then it is considering multiple numbers.
Can anyone please help me how to limit the size of whole numbers or if multiple digits entered also it need to consider only first two digits as it is considering when decimal point is placed.
You can use a custom directive for that.
Something like :
var app = angular.module('myApp', []);
app.controller('MainCtrl', function($scope) {
});
app.directive("marksRegex", function() {
var regexp;
return {
restrict: "A",
link: function(scope, elem, attrs) {
regexp = /^([0-9]([0-9]([\.]([0-9]([0-9]?)?)?)?)?)?$/;
var char;
elem.bind("keypress", function(event) {
if (event.which == 8) return;
char = String.fromCharCode(event.which);
if (!regexp.test(elem.val() + char))
event.preventDefault();
})
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="MainCtrl">
<label>Marks %:</label>
<input type="text" marks-regex>
</div>
</div>
add keypress event to input type id where this validation is required.
$("elementID").keypress(function (e) {
if (e.which != 8) {
var numberEntered = this.value + e.char;
var value = numberEntered.match(/^[0-9]{1,3}(\.[0-9]{0,2})?$/);
if (numberEntered.indexOf('.') > 2) {
return false;
}
if (value == null) {
return false;
}
else {
var x = parseFloat(value);
if (isNaN(x) || x < 0 || x > 100) {
return false;
}
}
}
return true;
});

How to get selected value from ng-autocomplete directive to controller

I am using a directive for auto complete / auto suggest in angular Js taken from http://demo.jankuri.com/ngAutocomplete/. It is working fine getting the data from server and filtering it. But I am facing problem into select and use that select item from the auto complete.
Here is the code of directive what I am using for this...
app.factory('ngAutocompleteService', ['$http', function($http)
{
var self = this;
self.getData = function (url, keyword) {
return $http.get(url, { query: keyword });
};
return self;
}])
app.directive('ngAutocomplete', ['$timeout','$filter','ngAutocompleteService',
function($timeout, $filter, ngAutocompleteService)
{
'use strict';
var keys = {
left : 37,
up : 38,
right : 39,
down : 40,
enter : 13,
esc : 27
};
var setScopeValues = function (scope, attrs) {
scope.url = base_url+attrs.url || null;
scope.searchProperty = attrs.searchProperty || 'skills';
scope.maxResults = attrs.maxResults || 10;
scope.delay = parseInt(attrs.delay, 10) || 300;
scope.minLenth = parseInt(attrs.minLenth, 10) || 2;
scope.allowOnlyResults = scope.$eval(attrs.allowOnlyResults) || false;
scope.placeholder = attrs.placeholder || 'Search...';
};
var delay = (function() {
var timer = 0;
return function (callback, ms) {
$timeout.cancel(timer);
timer = $timeout(callback, ms);
};
})();
return {
restrict: 'E',
require: '?ngModel',
scope: true,
link: function(scope, element, attrs, ngModel) {
setScopeValues(scope, attrs);
scope.results = [];
scope.currentIndex = null;
scope.getResults = function () {
if (parseInt(scope.keyword.length, 10) === 0) scope.results = [];
if (scope.keyword.length < scope.minLenth) return;
delay(function() {
ngAutocompleteService.getData(scope.url, scope.keyword).then(function(resp) {
scope.results = [];
var filtered = $filter('filter')(resp.data, {skills: scope.keyword});
for (var i = 0; i < scope.maxResults; i++) {
scope.results.push(filtered[i]);
}
scope.currentIndex = 0;
if (scope.results.length) {
scope.showResults = true;
}
});
}, scope.delay);
};
scope.selectResult = function (r) {
scope.keyword = r.skills;
ngModel.$setViewValue(r.skills);
scope.ngModel = r.skills;
ngModel.$render();
scope.showResults = false;
};
scope.clearResults = function () {
scope.results = [];
scope.currentIndex = null;
};
scope.hoverResult = function (i) {
scope.currentIndex = i;
}
scope.blurHandler = function () {
$timeout(function() {
if (scope.allowOnlyResults) {
var find = $filter('filter')(scope.results, {skills: scope.keyword}, true);
if (!find.length) {
scope.keyword = '';
ngModel.$setViewValue('');
}
}
scope.showResults = false;
}, 100);
};
scope.keyupHandler = function (e) {
var key = e.which || e.keyCode;
if (key === keys.enter) {
scope.selectResult(scope.results[scope.currentIndex]);
}
if (key === keys.left || key === keys.up) {
if (scope.currentIndex > 0) {
scope.currentIndex -= 1;
}
}
if (key === keys.right || key === keys.down) {
if (scope.currentIndex < scope.maxResults - 1) {
scope.currentIndex += 1;
}
}
if (key === keys.esc) {
scope.keyword = '';
ngModel.$setViewValue('');
scope.clearResults();
}
};
},
template:
'<input type="text" class="form-control" ng-model="keyword" placeholder="{{placeholder}}" ng-change="getResults()" ng-keyup="keyupHandler($event)" ng-blur="blurHandler()" ng-focus="currentIndex = 0" autocorrect="off" autocomplete="off">' +
'<input type="hidden" ng-model="skillIdToBeRated">'+
'<div ng-show="showResults">' +
' <div ng-repeat="r in results | filter : {skills: keyword}" ng-click="selectResult(r)" ng-mouseover="hoverResult($index)" ng-class="{\'hover\': $index === currentIndex}">' +
' <span class="form-control">{{ r.skills }}</span>' +
' </div>' +
'</div>'
};
}]);
I am unable to get value which is selected by ng-click="selectResult(r)" function. The value is showing into text field but not getting it into controller.
I was also using the same directive for showing the auto complete text box. I have tried the following for getting the selected value from auto complete.
in HTML
<div ng-controller="Cntrl as cntrl">
<ng-autocomplete ng-model="cntrl.selectedValue" url="url" search-property="keyword" max-results="10" delay="300" min-length="2" allow-only-results="true"></ng-autocomplete>
</div>
in JavaScript
app.controller('Cntrl', function($scope, $http) {
var self = this;
self.selectedValue = '';
$scope.getSelectedValue = function(){
console.log(self.selectedValue);
}
});
I hope this may help you.
I ran into the same issue. I ended up just watching the property from the details attribute in the ng-autocomplete input and it works pretty well.
$scope.$watch(function() {
return vm.location_result;
}, function(location) {
if (location) {
vm.location_list.push(location);
vm.location = '';
}
});
Fiddle Example: http://jsfiddle.net/n3ztwucL/
GitHub Gist: https://gist.github.com/robrothedev/46e1b2a2470b1f8687ad

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

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

Resources