I have signup form in my ionic app (the code available here), the signup view has it own controller. the password confirm has a Directive to check the match with the main password. the register button is disabled till the form is valid. but the form never becomes valid. the password_c input is always invalid.
<ion-view view-title="Register Form">
<ion-content>
<form ng-submit="signup()" name="regForm" novalidate>
<div>
<label for="email" .....></label>
<label for="password" ......></label>
<label for="password_c">
<input type="password" id="password_c" name="password_c" ng-model="registerForm.password_c" valid-password-c required>
<span ng-show="regForm.password_c.$error.required && regForm.password_c.$dirty">Please confirm your password.</span>
<span ng-show="!regForm.password_c.$error.required && regForm.password_c.$error.noMatch && regForm.password.$dirty">Passwords do not match.</span>
</label>
<button type="submit" ng-disabled="!regForm.$valid">
</div>
</form>
</ion-content>
</ion-view>
and this is the directive:
.directive('validPasswordC', function () {
return {
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
ctrl.$parsers.unshift(function (viewValue, $scope) {
var noMatch = viewValue != scope.regForm.password.$viewValue
ctrl.$setValidity('noMatch', !noMatch)
})
}
}
})
I do console.log the ng-show condition; when the passwords matchs, the condition becomes undefined.
console.log(!regForm.password_c.$error.required && regForm.password_c.$error.noMatch && regForm.password.$dirty)
separately; first the .required become undefined then .noMatch
I can't test it now, but I believe $parsers expects you to return a value unless you have a parser error.
Try to add a return in your code:
ctrl.$parsers.unshift(function (viewValue) {
var noMatch = viewValue != scope.regForm.password.$viewValue;
ctrl.$setValidity('noMatch', !noMatch);
return viewValue;
});
Related
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
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.
I have a form where the user needs to enter 2 times his email.
I found on the internet (but not able to find the link anymore) some angular code doing what I wanted.
The only issue is when the 2 emails are the same, one input still got the class
.ng-invalid.ng-dirty
So I can't submit the form.
My code is the following:
css:
.ng-invalid.ng-dirty{
border-color: #FA787E;
}
.ng-valid.ng-dirty{
border-color: #78FA89;
}
html:
<div ng-app="myApp">
<form name="formTemplate" novalidate>
<input id="email1" type="email" ng-model="currentForm.email1" name="email1" required>
<span ng-show="formTemplate.email1.$error.required && formTemplate.email1.$dirty">required</span>
<span ng-show="!formTemplate.email1.$error.required && formTemplate.email1.$error.email1 && formTemplate.email1.$dirty">invalid email</span>
<input id="email2" type="email" name="email2" ng-model="currentForm.email2" same-email required>
<span ng-show="formTemplate.email2.$error.required && formTemplate.email2.$dirty">Please confirm your email.</span>
<span ng-show="!formTemplate.email2.$error.required && formTemplate.email2.$error.noMatch && formTemplate.email1.$dirty">Emails do not match.</span>
</form>
</div>
javascript:
var app = angular.module('myApp', ['UserValidation']);
angular.module('UserValidation', []).directive('sameEmail', function () {
return {
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
ctrl.$parsers.unshift(function (viewValue, $scope) {
var noMatch = viewValue != scope.formTemplate.email1.$viewValue;
ctrl.$setValidity('noMatch', !noMatch)
})
}
}
})
Here a jsfiddle: http://jsfiddle.net/malamine_kebe/pq6fw04v/
Im update your jsfiddle. You don't return value from ctrl.$parsers.unshift.
ctrl.$parsers.unshift(function (viewValue, $scope) {
var noMatch = viewValue != scope.formTemplate.email1.$viewValue;
ctrl.$setValidity('noMatch', !noMatch);
return noMatch;
})
Updated jsfiddle
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();
});
}
};
});
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.