AngularJS: How to set validity based on custom boolean value - angularjs

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>

Related

scope.$eval() is undefined if both input fields have the same directive

I'm trying to validate two password input fields. Simply confirm that they are equal. (Suggest another approach if mine is way wrong)
I have implemented a directive with a simple validation that checks if the "confirm" password is the same as the original. But the directive also checks for other things, so I need to have both input fields to have it.
The problem is that when I have my directive on both input fields, I cannot read their model values through the attribute (to check if they match).
Here is a working demo without the directive on the first password:
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
});
app.directive('myDir', function() {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ctrl) {
ctrl.$validators.mismatch = function(modelValue, viewValue) {
// MAIN CODE:
return viewValue === scope.$eval(attrs.confirm);
};
ctrl.$validators.short = function(modelValue, viewValue) {
if (ctrl.$isEmpty(modelValue)) {
return true;
}
if (modelValue.length >= 3) {
return true;
}
return false;
}
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js"></script>
<div ng-app="myApp" ng-controller="myCtrl">
<form name="form1">
<input type="password" name="password1" ng-model="pass1"><br>
<input type="password" my-dir confirm="pass1" name="password2" ng-model="pass2"><br>
<pre>{{form1.password2.$error | json}}</pre>
<p ng-show="form1.password2.$error.mismatch" style="color:red">Passwords are different</p>
</form>
</div>
If I change the first filed to:
<input type="password" my-dir confirm="pass2" name="password1" ng-model="pass1">
to validate in both directions, then scope.$eval(attrs.confirm) becomes undefined for both fields.
Here is a demo of my issue:
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
});
app.directive('myDir', function() {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ctrl) {
ctrl.$validators.mismatch = function(modelValue, viewValue) {
// `scope.$eval(attrs.confirm)` always undefined
return viewValue === scope.$eval(attrs.confirm);
};
ctrl.$validators.short = function(modelValue, viewValue) {
if (ctrl.$isEmpty(modelValue)) {
return true;
}
if (modelValue.length >= 3) {
return true;
}
return false;
}
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js"></script>
<div ng-app="myApp" ng-controller="myCtrl">
<form name="form1">
<input type="password" my-dir confirm="pass2" name="password1" ng-model="pass1"><br>
<input type="password" my-dir confirm="pass1" name="password2" ng-model="pass2"><br>
<pre>{{form1.password2.$error | json}}</pre>
<p ng-show="form1.password2.$error.mismatch || form1.password.$error.mismatch" style="color:red">
Passwords are different
</p>
</form>
</div>
You need to do 2 things:
1. Add ng-model-options="{allowInvalid: true}" so invalid value will still update scope value.
2. Now you have problem that e.g. changing 2nd input wont trigger 1st re-validation. This is done using observe:
<body ng-controller="MainCtrl" ng-init="x = 0; y = 0">
<form name="form1">
<input type="password" my-dir="{{y}}" confirm="pass2" name="password1" ng-model="pass1" ng-model-options="{allowInvalid: true}"
ng-change="x = x + 1"><br>
<input type="password" my-dir="{{x}}" confirm="pass1" name="password2" ng-model="pass2" ng-model-options="{allowInvalid: true}"
ng-change="y = y + 1"><br>
and
attrs.$observe('myDir', function() {
ctrl.$validate();
});
http://plnkr.co/edit/ws4tVWGXfFNR2yqLRJN7?p=preview
P.S. for usual fields I would write my-dir="{{pass1}}" and then no need in $eval and ng-change, but for passwords... not sure

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>

AngularJS password validation

i'm learning from tutorial "Creating Apps With Angular, Node, and Token Authentication". And i've stuck. I found this code there:
html
<form name="register" class="form-signin" novalidate>
<h1 class="form-signin-heading text-muted">Register</h1>
<input type="email" ng-model="email" name="email"
class="form-control" placeholder="Email address"
required autofocus>
<p class="help-block"
ng-show="register.email.$dirty && register.email.$invalid">
Please enter a proper email.
</p>
<input type="password" name="password" ng-model="password"
class="form-control" placeholder="Password" required>
<input type="password" name="password_confirm" ng-model="password_confirm"
class="form-control" placeholder="Confirm Password"
validate-equals="password">
<p class="help-block"
ng-show="register.password_confirm.$invalid && register.password_confirm.$dirty">
please match the passwords.
</p>
<button ng-disabled="register.$invalid" class="btn btn-lg btn-primary btn-block" type="submit">
Submit
</button>
</form>
and js
angular.module('myApp', []).directive('validateEquals', function () {
return {
require: 'ngModel',
link: function (scope, element, attrs, ngModelCtrl) {
function validateEqual(value) {
var valid = (value === scope.$eval(attrs.validateEquals));
ngModelCtrl.$setValidity('equal', valid);
return valid ? value : undefined;
}
ngModelCtrl.$parsers.push(validateEqual);
ngModelCtrl.$formatters.push(validateEqual);
scope.$watch(attrs.validateEquals, function() {
ngModelCtrl.$setViewValue(ngModelCtrl.$viewValue);
});
}
};
});
this directive according to the video should give me a proper two-way validation for password and password_confirm inputs, but it doesn't (on video it does work, i'm confused). It validates well, when i change value of password_confirm but when i change password, validation not work. Here is plunker plunker. Question: What's wrong with that code? And how should i fix it?
The problem probably is in this line:
ngModelCtrl.$setViewValue(ngModelCtrl.$viewValue);
This line doesn't do anything. I gues sthat in a previous version of Angular, it retriggered the $parsers pipeline, and that in the current version, since it sets the view value to the same value that it already has, it has been optimized to not do anything.
This really looks like a hack to me. It should be replaced by something like
scope.$watch(attrs.validateEquals, function(firstPassword) {
var valid = (ngModelCtrl.$modelValue === firstPassword);
ngModelCtrl.$setValidity('equal', valid);
});
(not tested)
BTW, angular now has support for validators, which make things easier and don't force you to deal with parsers and formatters: https://docs.angularjs.org/api/ng/type/ngModel.NgModelController.
Solution found, Thanks to JB Nizet :)
solution :
angular.module('learningAngularApp')
.directive('validateEquals', function () {
return {
require: 'ngModel',
link: function (scope, element, attrs, ngModelCtrl) {
ngModelCtrl.$validators.validateEqual = function(modelValue, viewValue) {
var value = modelValue || viewValue;
var valid = (value === scope.$eval(attrs.validateEquals));
return valid;
}
scope.$watch(attrs.validateEquals, function () {
ngModelCtrl.$validate();
});
}
};
});

AngularJS validation directive

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.

Resources