AngularJS validation directive - angularjs

We have a validation directive that we use to validate the controls on Blur and viewContentLoaded event.
We persist the form values in local storage to remember the details when we navigate away from the form and come back
The problem is that, even though it remembers the details it doesn't re-validate the Firstname and Lastname when we load that view again.
Following is our original code:
<div>
<form name="form" class="form-horizontal">
<label class="control-label">First name</label>
<div class="controls">
<input id="firstName" name="FirstName" ng-model="order.FirstName" type="text" validate="alphabeticOnly" maxLength="30" required/>
<span class="help-block" ng-show="form.FirstName.$dirty && form.FirstName.$invalid">Please enter valid Firstname</span>
</div>
</div>
<div class="control-group">
<label class="control-label">Last name</label>
<div class="controls">
<input id="lastName" name="LastName" ng-model="order.LastName" type="text" validate="alphabeticOnly" maxLength="30" required/>
<span class="help-block" ng-show="form.LastName.$dirty && form.LastName.$invalid">Please enter valid Lastname</span>
</div>
</div>
</form>
Confirm
The next function just saves the order and redirects to another page.
We have a $watch on $scope.order that saves the data in local storage to remember.
Directive:
.directive('validate', ['validationService', function(validationService) {
function validate(elm) {
var fn = elm.attr("validate");
var value = elm.val();
return validationService[fn](value);
}
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
function triggerValidations(){
checkValidity();
ctrl.$parsers.unshift(function(viewValue) {
checkValidity();
return viewValue;
});
ctrl.$formatters.unshift(function(viewValue) {
checkValidity();
return viewValue;
});
}
function checkValidity(){
if (elm.val().length > 0)
{
var isValid = validate(elm);
ctrl.$setValidity('validate', isValid);
console.log(" here i am - CheckValidity", attrs.id, elm.val(), isValid );
//scope.$apply();
}
}
$rootScope.$on('$viewContentLoaded', triggerValidations);
elm.bind('blur', function(event) {
scope.$apply(function() {
ctrl.$setValidity('validate', validate(elm));
});
});
}
};
}])
If we add $scope.apply it gives an error "$digest already in progress"
At the end, we want to validate the form when someone lands onto the page.

Related

AngularJS: Hiding ng-message until hitting the form-submit button

This is a typical example of the use of ng-messages in AngularJS (1.x):
<form name="demoForm">
<input name="amount" type="number" ng-model="amount" max="100" required>
<div ng-messages="demoForm.amount.$error">
<div ng-message="required">This field is required</div>
</div>
<button type="submit">test submit</button>
</form>
see: http://jsfiddle.net/11en8swy/3/
I now want to change this example so the "This field is required" error only shows when the field is touched ($touched) or the user hits the submit button.
I cannot use the ng-submitted class on the form since the validation error prevents the submitting of the form.
How should I do this?
Thanks
You can do this using ng-show:
<div ng-messages="demoForm.amount.$error" ng-show="demoForm.amount.$touched">
<div ng-message="required">This field is required</div>
</div>
And use a custom directive. See a working demo:
var app = angular.module('app', ['ngMessages']);
app.controller('mainCtrl', function($scope) {
});
app.directive('hasFocus', function($timeout) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attr, ctrl) {
element.on('focus', function() {
$timeout(function() {
ctrl.hasFocusFoo = true;
})
});
element.on('blur', function() {
$timeout(function() {
ctrl.hasFocusFoo = false;
})
});
}
}
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular-messages.js"></script>
<body ng-app="app" ng-controller="mainCtrl">
<form name="demoForm">
<input name="amount" type="number" ng-model="amount" max="100" required has-focus>
<div ng-messages="demoForm.amount.$error" ng-show="demoForm.amount.$touched || demoForm.amount.hasFocusFoo">
<div ng-message="required">This field is required</div>
</div>
<button type="submit">test submit</button>
</form>
</body>
The directive is basically setting another hasFocusFoo field on the ngModel controller then we can easily use that directive.
Ah, at the PC at last.
https://plnkr.co/edit/EX3UmoAOKmTKlameBXRa?p=preview
<form name="mc.form">
<input type="text" name="empty" ng-model="mc.empty" required />
<label ng-show="mc.form.empty.$dirty && mc.form.empty.$error.required">i'm empty</label>
</form>
MainController.$inject = ['$timeout'];
function MainController($timeout) {
var vm = this;
$timeout(function(){
vm.form.$setPristine();
});
vm.submit = function(){
if(vm.form.$valid){
alert('yay');
}else{
(vm.form.$error.required || []).forEach(function(f){
f.$dirty = true;
});
}
}
}
Here is how I handle this task in my solution. form.$setPristine() - sets the field in a pristine state, so field isn't $dirty and error hidden. But after submit I manually state required fields in a $dirty state, so errors become visible. + if you type something, and delete it after, the error would be visible without submitting a form.

How do I get multiple input values in the same directive?

EDIT: Below is the working example for anyone looking. I have live validation on the promoCode with the directive that takes in 2 input values.
Hello I'm stuck on a problem. I have 2 inputs, an email and a promo code and one directive that checks if the promo code and associated email are valid. However I am stuck in how to pass in the email value as well. I've got it half way where I am able to check the promo code with a hardcoded email value. Am I going to have to rewrite how I do this check or am I missing something obvious to get the email value? Is this even a right approach? I am new to Angular.
To clarify: The API call happens when the user starts typing in a promo code. Basically how do I grab the email value and send it along to the factory? I've looked and tried scope.$watch and was able to see the values changing for each input but was unable to store it.
I have looked here How to get evaluated attributes inside a custom directive and tried that approach but was getting "undefined" for email and promocode values.
HTML
<input type="text" class="form-control" name="email" id="email" placeholder="Enter Email" maxlength="254" ng-model="registrationData.email" required >
<input type="text" class="form-control" name="promoCode" id="promo-code" placeholder="Parking Program Code" ng-model="registrationData.promo_code" ng-model-options="{ debounce: 500 }" promo-email='registrationData.email' promo-validation>
<div ng-show="validate.promoCode.$touched || validate.promoCode.$dirty">
<div ng-messages="validate.promoCode.$error" style="color:maroon" role="alert">
<div ng-message="promoCode">Code invalid</div>
</div>
<div ng-messages="validate.promoCode.$pending">
<div ng-message="promoCode">Checking promo test...</div>
</div>
Directive
.directive('promoValidation',['isPromoValid', function(isPromoValid)
{
return {
restrict: 'A',
require: 'ngModel',
scope:
{
promoEmail:'='
},
link: function(scope, element, attrs, ngModel) {
element.bind('focus', function() {
ngModel.$asyncValidators.promoCode = function(promoCode) {
return isPromoValid(scope.promoEmail, promoCode);
})
}
}}
]);
Factory
.factory('isPromoValid',['$q', '$http', 'SERVICE_BASE', function($q, $http, SERVICE_BASE) {
var apiUrl = SERVICE_BASE + '/users/ValidatePromoCode';
return function(email, promoCode) {
var deferred = $q.defer();
if(!promoCode)
{
deferred.resolve(); //promoCode is optional so I have this check if the user adds a promoCode then deletes it. It removes the error message
}
else {
$http.get(apiUrl , {params: { promo_code: promoCode, email: email }}).then(function validPromo(data){
var isPromoValid = data.data.data.valid;
if(isPromoValid) {
deferred.resolve();
}
else {
deferred.reject();
}
});
}
return deferred.promise;
};
}]);
You need to change your directive like as-
.directive('promoValidation',['isPromoValid', function(isPromoValid
{
return {
restrict: 'A',
require: 'ngModel',
scope: {
promoEmail:'='
},
link: function(scope, element, attrs, ngModel) {
element.bind('focus', function() {
//console.log(ngModel.email);
//console.log(scope.promoEmail); // pass this email to your validator factory
ngModel.$asyncValidators.promoCode = isPromoValid;
})
}
}}
]);
On html, use this directive only for promocode input field like as-
<input type="text" class="form-control" name="email" id="email" placeholder="Enter Email" maxlength="254" ng-model="registrationData.email" required >
<input type="text" class="form-control" name="promoCode" id="promo-code" placeholder="Parking Program Code" ng-model="registrationData.promo_code" ng-model-options="{ debounce: 500 }" promo-email='registrationData.email' promo-validation>
<div ng-show="validate.promoCode.$touched || validate.promoCode.$dirty">
<div ng-messages="validate.promoCode.$error" style="color:maroon" role="alert">
<div ng-message="promoCode">Code invalid</div>
</div>
<div ng-messages="validate.promoCode.$pending">
<div ng-message="promoCode">Checking promo test...</div>
</div>
you can improve this code as much as you needed.

Password Confirmation Directive

Here is the code I'm using for the directive:
var compareTo = function() {
return {
require: "ngModel",
scope: {
otherModelValue: "=compareTo"
},
link: function(scope, element, attributes, ngModel) {
ngModel.$validators.compareTo = function(modelValue) {
console.log(modelValue + ":" + scope.otherModelValue);
return modelValue == scope.otherModelValue;
};
scope.$watch("otherModelValue", function() {
ngModel.$validate();
});
}
};
};
app.directive("compareTo", compareTo);
Here is my html:
<div class="form-group">
<label>Password</label>
<span>Must contain at least eight characters, including uppercase, lowercase letters and numbers</span>
<input type="password"
class="form-control"
name="password"
ng-model="signUpPass1"
ng-pattern="/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{8,})/"
required>
<div ng-messages="signUpForm.password.$error" class="formMsgContainer">
<span class="formMsg" ng-message="pattern">Passwords Does Not Meet the Criterias!</span>
</div>
</div>
<div class="form-group">
<label>Confirm Password</label>
<input type="password"
class="form-control"
name="conPass"
ng-model="signUpPass2"
compare-to="signUpPass1"
required>
<div ng-messages="signUpForm.conPass.$error" class="formMsgContainer">
<span class="formMsg" ng-message="compareTo">Passwords Do Not Match!</span>
</div>
</div>
However the compareTo directive doesn't work. Looking at the console log I put in the directive, it prints out the string for pass2 and undefined for pass1. Such as aaaa:undefined. This means that they will never be equal and thus there will always be an error. So there must be something wrong with the statement scope.otherModelValue but I can't seem to figure out what is wrong.
Thanks
Try this directive. I haven't tested it, so if it doesn't work, please tell me.
app.directive("compareTo", function() {
return {
require: "ngModel",
link: function(scope, element, attributes, controller) {
scope.$watch(attributes.ngModel, function(value) {
controller.$setValidity('compare', (element.val() === value));
});
}
};
});
You can then use the "compare" in the ng-message to output an error.
To create your own checks try directive use-form-error. It is easy to use and will save you a lot of time.
Live example jsfiddle
<form name="ExampleForm">
<label>Password</label>
<input ng-model="password" required />
<br>
<label>Confirm password</label>
<input ng-model="confirmPassword" required />
<div use-form-error="isSame" use-error-expression="password && confirmPassword && password!=confirmPassword" ng-show="ExampleForm.$error.isSame">Passwords Do Not Match!</div>
</form>

Auto tab in input type text field not working in Ionic

Currently what I'm doing is like this for HTML,
<label class="item item-input">
<input type="text" maxlength="1" ng-model="user.name" maxlength="1" input-move-next-on-maxlength></input>
</label>
<label class="item item-input">
<input type="text" maxlength="1" ng-model="user.email" maxlength="1" input-move-next-on-maxlength></input>
</label>
and the .js looks like this
.directive("inputMoveNextOnMaxlength", function() {
return {
restrict: "A",
link: function($scope, element) {
element.on("label", function(e) {
if(element.val().length == element.attr("maxlength")) {
var $nextElement = element.next();
if($nextElement.length) {
$nextElement[0].focus();
}
}
});
}
}
})
But, the result is the auto tab is not working. I also tried removing the <label>, that worked. I'm wondering does it happen because of the input is at other class or does my code have an error.
Try with this solution, It worked for me
HTML:
<div ng-app="autofocus">
<label>Name:</label>
<input ng-model="maxLengthReach"></input>
<br/><br/>
<label>Title:</label>
<input autofocus-when></input>
</div>
Javascript:
var app = angular.module('autofocus', []);
app.directive('autofocusWhen', function () {
return function (scope, element, attrs) {
scope.$watch('maxLengthReach', function(newValue){
if (newValue.length >= 5 ) {
element[0].focus();
}
});
}
});
Adjust the length based upon your requirement

AngularJS: How to set validity based on custom boolean value

I would like to set the validity of a form element based on a custom boolean value. Consider the following password fields:
<input type="password" name="password" ng-model="user.password" required>
<input type="password" name="passwordRepeat" ng-model="user.passwordRepeat" required>
I would like to mark the second input field valid if the repeated password matches the original password. Something like:
<input type="password" name="passwordRepeat" ng-model="user.passwordRepeat" my-validation-check="user.passwordRepeat === user.password" required>
I was not able to find any Angular directive for this purpose. Any ideas? Perhaps create my own directive for this? Unfortunately, I'm not an Angular expert... it should be something like this:
angular.module('app').directive('myValidationCheck', function() {
return {
scope: true,
require: 'ngModel',
link: function(scope, elm, attrs, ngModel) {
// eval and watch attrs.myValidationCheck
// and use ngModel.$setValidity accordingly
}
};
});
Thanks!
I have spent quite a bit of time finding the best answer based on your answers below (thanks a lot!). What did the trick for me was simply:
angular.module('myApp').directive('myValidationCheck', function() {
return {
scope: {
myValidationCheck: '='
},
require: 'ngModel',
link: function(scope, elm, attrs, ngModel) {
scope.$watch('myValidationCheck', function(value) {
ngModel.$setValidity('checkTrue', value ? true : false);
});
}
};
});
for
<input type="password" name="passwordRepeat" my-validation-check="user.password === user.passwordRepeat" ng-model="user.passwordRepeat" required>
And this is really flexible. You can use anything you want in my-validation-check, e.g. make sure a checkbox is checked or any more complex expression is true.
Hope this helps not just myself.. :-)
Why do you need special directive for it?
Why not make so:
<div ng-controller="MyCtrl">
<form name="myForm" ng-submit="processForm()">
<input type="password" ng-model="password" placeholder="password" required/>
<input type="password" ng-model="repeatedPassword" placeholder="repeat password" required/>
<input type="Submit" value="Submit" ng-disabled="passwordsMissmatched()"/>
<span ng-show="passwordsMissmatched()">
Password mismatched
</span>
</form>
</div>
And your JS:
function MyCtrl($scope) {
$scope.passwordsMissmatched = function(){
return $scope.password && $scope.repeatedPassword
&& ($scope.password != $scope.repeatedPassword);
}
$scope.processForm = function(){
if($scope.password == $scope.repeatedPassword){
alert("Form processing..");
}
};
}
This approach should work like a charm.
I've created JSFiddle for you.
Please see demo below
var app = angular.module('app', []);
app.directive('mcheck', function() {
return {
require: 'ngModel',
link: function(scope, elm, attrs, ngModel) {
scope.$watch(attrs.ngModel, function(value) {
if (value == attrs.mcheck) {
ngModel.$setValidity('notEquals', true);
} else {
ngModel.$setValidity('notEquals', false);
}
});
}
};
});
app.controller('fCtrl', function($scope) {
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="fCtrl">
<form novalidate name="login">
<input type="text" name="password" ng-model="user.password" mcheck="{{user.passwordRepeat}}" required>
<input type="text" name="passwordRepeat" ng-model="user.passwordRepeat" mcheck="{{user.password}}" required>
<HR/>
<span ng-show="login.password.$error.notEquals && login.passwordRepeat.$error.notEquals && login.$dirty">Passwords are not equal</span>
<HR/>
</form>
</div>
</div>

Resources