Angularjs initial form validation with directives - javascript

I have a validation directive called valid-number that is used to set the validity of a form using $setValidity - this works fine for any text values that I type into the input box that have the directive applied to as an attribute.
The HTML is
<form name="numberForm">
<input name="amount" type="text" ng-model="amount" required valid-number /></form>
The directive is as follow
angular.module('test',[]).directive('validNumber',function(){
return{
require: "ngModel",
link: function(scope, elm, attrs, ctrl){
var regex=/\d/;
ctrl.$parsers.unshift(function(viewValue){
var floatValue = parseFloat(viewValue);
if(regex.test(viewValue)){
ctrl.$setValidity('validNumber',true);
}
else{
ctrl.$setValidity('validNumber',false);
}
return viewValue;
});
}
};
});
However, I would also like the validation to be triggered and set the css to an invalid clsss if the value the input box is initialised to when the page is first loaded is invalid, eg if I set $scope.amount = 'not a number' I would expect the input box to have had the directive applied to it, but no joy. In order for not a number to be highlighted as invalid I have to make a change to the contents of the input, which triggers the directive.
How can I ensure the directive applies to whatever the <input> is initialised with?
A full code example is here;
http://jsfiddle.net/JW43C/5/

$parsers array contains a list of functions that will be applied to the value that model receives from the view (what user types in), and $formatters array contains the list of functions that are being applied to the model value before it's displayed in the view.
In your directive you correctly used the $parsers array, but you also need to add the $formatters array if you want the initial value to be validated:
angular.module('test',[]).directive('validNumber',function(){
return{
require: "ngModel",
link: function(scope, elm, attrs, ctrl){
var regex = /^\d$/;
var validator = function(value){
ctrl.$setValidity('validNumber', regex.test(value));
return value;
};
ctrl.$parsers.unshift(validator);
ctrl.$formatters.unshift(validator);
}
};
});
Demo plunker

You can simply call your verification function during the linking phase, like in this fiddle :
link: function(scope, elm, attrs, ctrl) {
var regex=/\d/;
var verificationFunction = function(viewValue) {
var floatValue = parseFloat(viewValue);
if(regex.test(viewValue)) {
ctrl.$setValidity('validNumber',true);
return viewValue;
}
else {
ctrl.$setValidity('validNumber',false);
return undefined;
}
};
ctrl.$parsers.unshift(verificationFunction);
verificationFunction();
}

After (>=) angular 1.3.1 version was released you could implement that behaviour with a little bit correct way, following angular validation directives style (e.g. required, maxlength).
In that case you have to append your validator as property of $validators array and there are no need in $parsers or $formatters anymore:
var app = angular.module('test', []);
app
.directive('validNumber', function() {
return {
require: "ngModel",
link: function(scope, elm, attrs, ctrl) {
var regex = /^\d+$/;
ctrl.$validators['validNumber'] = function(modelValue, viewValue) {
return regex.test(viewValue);
};
}
};
});
app.controller('NumberCtrl', NumberCtrl);
function NumberCtrl($scope) {
$scope.amount = '5z';
};
input.ng-invalid {
background-color: #FA787E;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.min.js"></script>
<div ng-app="test">
<div ng-controller="NumberCtrl">
<div ng-form name="numberForm">
<input name="amount"
type="text"
ng-model="amount"
required
valid-number />
<span ng-show="numberForm.amount.$error.validNumber">
Doesn't look like an integer
</span>
</div>
</div>
</div>

Related

Angular directive for range validation

I am trying to make a directive for an input to limit the value between 1-99. On the same input I also have another directive that converts the value to a percentage and am not sure if that is what is getting in the way.
The directive is simple (taken basically from the Angular website):
(function() {
'use strict';
angular
.module('app.model')
.directive('inputRange', inputRange);
function inputRange() {
return {
require: 'ngModel',
restrict: 'A',
link: function(scope, elm, attrs, ctrl) {
var INTEGER_REGEXP = /^-?\d+$/;
ctrl.$validators.inputRange = function(modelValue, viewValue) {
if (ctrl.$isEmpty(modelValue)) {
// consider empty models to be valid
return true;
}
if (INTEGER_REGEXP.test(viewValue)) {
// it is valid
return true;
}
// it is invalid
return false;
};
}
}
}
});
And the section of html with the input field (which is paired with a slider):
<form name="form">
<div sc-slider
ng-model="vm.baseline"
min="0.01"
max="0.99"
initial="{{vm.baseline}}"
step="0.01"
uib-tooltip="The initial estimate of the KIQ's likelihood - prior to any indicator observations."
tooltip-popup-delay="200"
tooltip-popup-close-delay="200"
tooltip-placement="bottom"></div>
<input to-percent
input-range
name="baseline"
style="text-align:center;"
type="text"
min="1"
max="99"
class="form-control"
ng-model="vm.baseline"></input>
<span ng-show="form.baseline.$error.inputRange">The value is not a valid integer!</span>
<span ng-show="form.baseline.$error.min || form.baseline.$error.max">
The value must be in range 1 to 99!</span>
</form>
I have read on SO about priority for directives that share an input but I don't think that is necessarily an issue here. But when I enter a value greater than 99 I'd expect one of the spans below to show up, but nothing is appearing. And my other directive works fine all of the time. Any help is appreciated.
Make your life easier and use ng-max and ng-min attributes on the input element.

Encrypt ngModel value in AngularJS

I want to encrypt (using any algorithm) value of a ngModel. Only the $modelValue should be encrypted and view value should be plain text.
To do so, I came up with a small custom directive:-
angular.module('utilityModule').directive('encrypt', function() {
var aesUtil = new AesUtil(128, 10);
return {
restrict: 'A',
require: 'ngModel',
replace: false,
compile: function(tElem, tAttrs) {
var modelName = tAttrs['ngModel'];
var pattern = tAttrs['ngPattern']; // to check if there is ngPattern directive used.
return {
pre: function(scope, element, attrs, fn) {
// to avoid encrypting on every key press.
fn.$options = {
updateOn: 'blur'
};
fn.$parsers.push(function(value) {
//encrypt
console.log('parser invoked');
return value ? aesUtil.encrypt(modelName, modelName, modelName, value) : value;
});
fn.$formatters.push(function(value) {
//decrypt
console.log('formatter invoked');
return value ? aesUtil.decrypt(modelName, modelName, modelName, value) : value;
});
fn.$validators.pattern = function(){
// trying to overrule ngPattern directive. DOESN'T HELP!!
return true;
};
// Just for playing around
fn.$validators.amyValid = function(modelValue, viewValue) {
console.log('Custom validator invoked. modelValue=' + modelValue + ' and viewValue=' + viewValue);
return true;
};
},
post: function(scope, element, attrs, fn) {}
};
}
};
});
The directive works except when we have ngPattern used alongwith the ngModel directive. For example:-
<div class="table-responsive" ng-form="testForm">
<input name="test" type="text" ng-model="test" encrypt ng-pattern="/^[0-9]+$/"/>
<br>
{{test}}
</div>
My expectations:-
ngPattern directive should validate using the $viewValue instead of $modelValue.
How can I override the 'patternDirective' directive present in core angular.js?
Or any other suggestions...
UPDATE 1
Just realized that not just ngPattern, all other validations (maxLength, minLength, max, min) should be applied on view value only
UPDATE 2
My debugger shows that the value passed to patternDirective validator is the encrypted one. Please see the attached screenshot.
UPDATE 3
Upgrading to angularjs 1.4.5 fixed the problem. I believe that 1.3.x has validation on model value and not view value.
Upgrading to angularjs 1.4.5 fixed the problem. I believe that 1.3.x has validation on model value and not view value.

ngModel formatters change the $scope

Having an input with ngModel which has $formatters and $parsers -
link: function(scope, element, attrs, ngModel) {
//format text going to user (model to view)
ngModel.$formatters.push(function(obj) {
return obj.first;
});
}
After I make any change in the input , I miss this model on the scope , mean - {{person.first}} display nothing .
Here is the full code -
var myAppModule = angular.module('myApp', []);
myAppModule.controller('myCtrl',function ($scope) {
$scope.person = {
first: 'Alice',
last: 'Bob'
}
})
.directive('myDrtv',function(){
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
var _ngModel = ngModel;
//format text going to user (model to view)
ngModel.$formatters.push(function(obj) {
return obj.first;
});
//format text from the user (view to model)
ngModel.$parsers.push(function(value) {
return value;
});
}
}
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="myCtrl">
Name : <input my-drtv ng-model="person" type="text"><br>
{{person.first}}
</div>
</div>
How could I change the input and still see it in {{person.first}} ?
Please don't answer me change it to ng-model="person.first" I looking solution with - ng-model="person"
Change your input to the following:
<input my-drtv ng-model="person.first" type="text">
They way you have it, you are clobbering person and changing it from an object to a string.
Edit: If you want to keep first name and last name separate, then do something like this:
<input my-drtv ng-model="fullname" type="text">
and then in link, watch for changes and update person:
scope.$watch('fullname', function(nv) {
person.first = extractFirstName(nv);
person.last = extractLastName(nv);
});
(I left it to you to supply the extract functions).

Angular directive change ngmodel value to undefined

I'm using this form
<div ng-controller="TestController">
<form ng-submit="saveForm()">
<input type="text" name="test" ng-model="testData" test="{{testValue}}"/>
<input type="submit" value="Save"/>
</form>
</div>
and this angular code:
app.controller('TestController', [ '$scope', function($scope) {
$scope.testData="testValue";
$scope.saveForm = function(){
console.log($scope.testData);
}
}]);
app.directive('test', function($parse) {
return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
ctrl.$validators.validators = function(modelValue, viewValue) {
};
}
};
});
And after changing value in input and clicking save I'm getting undefined value in $scope.testData. Why and how to avoid that?
I was trying to add:
scope.testData = viewValue;
inside function:
ctrl.$validators.validators
But it doesn't work - only by turns.
Your validator needs to return true for the value to be set, otherwise it will be undefined
If the validity changes to invalid, the model will be set to undefined, unless ngModelOptions.allowInvalid is true. If the validity changes to valid, it will set the model to the last available valid modelValue, i.e. either the last parsed value or the last value set from the scope.
ctrl.$validators.validators = function(modelValue, viewValue) {
return modelValue !== '';
};

Prepopulate AngularJS form with invalid data

My model contains some data that does not passes the form's validation (say an invalid email address that comes from the server). I still want to show this invalid model data to the user so they get a chance to fix it.
Minimal example:
<form ng-init="email='foo'">
<input type="email" ng-model="email"></input>
</form>
How do I get the input to show the initial invalid model value?
JS Fiddle: http://jsfiddle.net/TwzXV/4/
This behaviour is reported as a bug. https://github.com/angular/angular.js/issues/2841
You can go around this behaviour by creating a directive UNTIL this bug is fixed :)
I got this from google mailing list
http://jsfiddle.net/nalberg/XccGJ/
app.directive('displayInvalid', function($parse, $filter) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elm, attrs, model) {
var displayed = false;
scope.$watch(attrs.ngModel, function(newValue, oldValue, scope) {
// only set once... on initial load
if(displayed == false && oldValue != undefined){
displayed = true;
elm.val(model.$modelValue);
}
});
}
}
})

Categories

Resources