Directive-validator doesn't catch empty field but filled field - angularjs

I have the following AngularJS code. It should check if input field is empty when I press Submit button. Submit button broadcasts custom event that directive successfully catches. But it doesn't work when field is empty. It reaches ctrl.$parsers.unshift when I start typing and my field becomes theForm.name.$invalid===true. It seems to work the opposite way.
define(['angular'], function (angular) {
"use strict";
var requiredValidator = angular.module('RequiredValidator', []);
requiredValidator.directive('validateRequired', function () {
var KEY_ERROR = "required";
return {
scope: {
validateRequired: '=validateRequired'
},
require: 'ngModel',
link: function (scope, elem, attr, ctrl) {
function validate(value) {
var valid = !value || value === '' || value === null;
ctrl.$setValidity(KEY_ERROR, valid);
return value;
}
scope.$on('validateEvent', function (event, data) {
if (scope.validateRequired.$submitted) {
console.log("Reachable block when submitted");
ctrl.$parsers.unshift(function (value) {
console.log("Unreachable when input is empty");
return validate(value);
});
ctrl.$formatters.unshift(function (value) {
return validate(value);
});
}
});
}
};
});
return requiredValidator;
});
Form field snippet:
<div>
<input type="text" name="name"
data-ng-class="{ error : theForm.name.$invalid}"
data-ng-model="customer.name"
data-validate-required="theForm">
<span data-ng-show="theForm.name.$invalid" class="error">{{getInputErrorMessage(theForm.name.$error)}}</span>
</div>

You actually don't need such a complex directive for your szenario. You could also handle the logic within a controller like so:
var app = angular.module('form-example', ['ngMessages']);
app.controller('FormCtrl', function($scope) {
var vm = this;
vm.submit = function(form) {
if (form.$invalid) {
angular.forEach(form.$error.required, function(item) {
item.$dirty = true;
});
form.$submitted = false;
} else {
alert('Form submitted!');
}
};
});
label,
button {
display: block;
}
input {
margin: 5px 0;
}
button {
margin-top: 10px;
}
form {
margin: 10px;
}
div[ng-message] {
margin-bottom: 10px;
color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular-messages.js"></script>
<form ng-app="form-example" name="myForm" ng-controller="FormCtrl as vm" novalidate="" ng-submit="vm.submit(myForm)">
<label for="username">Username</label>
<input id="username" type="text" name="username" ng-model="vm.username" required="" />
<div ng-messages="myForm.username.$error" ng-if="myForm.username.$dirty" role="alert">
<div ng-message="required">Username is required</div>
</div>
<label for="email">E-Mail</label>
<input id="email" type="email" name="email" ng-model="vm.email" required="" />
<div ng-messages="myForm.email.$error" ng-if="myForm.email.$dirty" role="alert">
<div ng-message="required">E-Mail is required</div>
<div ng-message="email">Your E-Mail is not valid</div>
</div>
<button type="submit">Send</button>
</form>
This requires to use at least AngularJS version 1.3.0 since I use the $submitted property of the internal FormController. For more information check the documentation on the FormController. I also use ngMessages which was also introduced in 1.3. This is pretty helpful if you want to display messages in forms in respect to errors.

Related

Prevent Form Submit if Phone Number is Invalid - Angularjs

I am wanting to prevent form submission if the phone input is invalid per my ng-pattern WITHOUT using disabled on the button. I have tried adding logic to my submitForm function to no avail. Something along the lines of...
Adding a validation function to my angular code:
const validPhone = function (phone) {
var re = /^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/;
return re.test(phone);
}
And adding some logic to my submit function like...
if (!this.validPhone(main.contact.phone)) {
$window.alert("Please a valid phone number!");
return false;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
<script>
(function () {
"use strict";
angular.module("rzApp", []).controller("MainCtrl", MainCtrl); // as main.
function MainCtrl($document, $timeout, $window) {
var main = this;
main.now = new Date();
main.contact = {
phone: "##phone##",
company: "##company##"
};
main.submitForm = _submitForm;
function _submitForm() {
if (!main.contact.phone) {
$window.alert("Please enter your phone number!");
return false;
}
if (!main.contact.company) {
$window.alert("Please enter your company!");
return false;
}
$timeout(function () {
$document[0].forms[0].submit();
}, 0);
}
}
})();
</script>
<form method="post" id="f" novalidate name="mainForm">
<div class="input-field">
<input type="text" placeholder="Phone *" value="##phone##" name="phone"
ng-model="main.contact.phone" required
style="margin-bottom: 0; margin-top: 16px"
ng-pattern="/^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/im" />
<small class="form-text rz-error" ng-show="mainForm.phone.$error.required">This
field is
required.</small>
<small class="form-text rz-error" ng-show="mainForm.phone.$error.pattern">
Doesn't look like a valid phone number!</small>
</div>
<div class="input-field">
<input type="text" placeholder="Company *" name="company"
ng-model="main.contact.company" value="##company##" required
style="margin-bottom: 0; margin-top: 16px" />
<small class="form-text rz-error"
ng-show="mainForm.company.$error.required">This field is
required.</small>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="callback_request"
value="yes" style="margin-bottom: 0; margin-top: 16px" />
<label class="form-check-label" for="flexCheckDefault">
I would like a call & a free audit.
</label>
</div>
<div class="input-field">
<input type="submit" class="button alt" value="Download Now"
ng-click="main.submitForm()" style="margin-bottom: 0; margin-top: 16px" />
</div>
<div style="margin-top: 10px">
<small><em class="text-muted">* Denotes a required field.</em></small>
</div>
</form>
Do something like:
<form (keydown.enter)="validateFunction($event)"></form>
Then put your logic in validate function and do $event.preventDefault() if needed.
I removed the disabled from the button and added logic to check for a valid phone number to my angular.js code:
<script>
(function () {
"use strict";
angular.module("rzApp", []).controller("MainCtrl", MainCtrl); // as main.
function MainCtrl($document, $timeout, $window) {
var main = this;
main.now = new Date();
main.contact = {
phone: "##phone##",
company: "##company##"
};
main.submitForm = _submitForm;
main.validPhone = _validPhone;
function _submitForm() {
if (!main.validPhone(main.contact.phone)) {
$window.alert("Please enter a valid phone number!");
return false;
}
if (!main.contact.company) {
$window.alert("Please enter your company!");
return false;
}
$timeout(function () {
$document[0].forms[0].submit();
}, 0);
}
function _validPhone(phone) {
const re =
/^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/;
return re.test(phone);
}
}
})();
</script>

Angular set pristine doesn't work as expected

I am trying to create a custom validation on one of my inputs. Everything works as expected but the error css class is still applied. I tried with $setPristine()
and $setUntouched() first on the input (didn't work) and then on the form. The perfect solution will be if I can reset the style in ng-change.
self.changeTransferNumber = function () {
if (validateRoutingNumber(self.bankTransferNumber)) {
self.bankInfoForm.bankTransferNumber.$error.validateTransferNumber = false;
self.bankInfoForm.$setPristine();
self.bankInfoForm.$setUntouched();
} else {
self.bankInfoForm.bankTransferNumber.$error.validateTransferNumber = true;
}
}
<form name="$ctrl.bankInfoForm" novalidate ng-submit="$ctrl.saveInfo($event)">
<input type="text" ng-model="$ctrl.bankTransferNumber" ng-disabled="$ctrl.disableTextEdit" name="bankTransferNumber" required ng-change ="$ctrl.changeTransferNumber()"/>
<div ng-messages="$ctrl.bankInfoForm.bankTransferNumber.$error" ng-show="$ctrl.bankInfoForm.bankTransferNumber.$dirty">
<div ng-message = "validateTransferNumber" >The transfer number is not correct</div>
</div>
</form>
You need to pass your Form name in html:-
<form role="form" name="Form" id="Form" ng-submit="Form.$valid && saveFormData($event,Form)" novalidate autocomplete="off">
in Your controller /js file :-
$scope.saveFormData = function (event,myForm) {
//after your whole logic
....
//to reset form
myForm.$setPristine();
}
What i understand from this question....
Why not create a custom validator using a directive? This way it's easily reusable.
(function() {
'use strict';
angular.module('myApp', []);
angular.module('myApp').controller('MyController', MyController);
MyController.$inject = [];
function MyController() {
var main = this;
main.routingNumber = '2';
}
angular.module('myApp').directive('validateRoutingNumber', function() {
return {
restrict: 'A',
require: {ngModel: ''},
bindToController: true,
controllerAs: '$dir',
controller: function() {
var $dir = this;
$dir.$onInit = function() {
createValidators();
};
function createValidators() {
$dir.ngModel.$validators.routingNumber = function(modelValue, viewValue) {
var value = modelValue || viewValue;
//without this if-else it would become a required field
if (value) {
return value.toString().length >= 3 && value.toString().indexOf('1') !== -1;
} else {
return true;
}
}
}
}
};
});
}());
input.ng-invalid {
border: 1px red solid;
background-color: red;
color: white;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<body ng-app="myApp">
<div ng-controller="MyController as main">
<form name="frmMain">
<input type="text" ng-model="main.routingNumber" name="routingNumber" validate-routing-number>
<span ng-if="frmMain.routingNumber.$invalid">Needs to have at least three characters and at least one '1'</span>
</form>
</div>
</body>

How to asynchronously validate form in Angular after submit?

I have a registration form validating client-side that works fine. My app has also a server-side validation which returns the client a JSON with errors for each field.
I try to handle them like this:
// errors = {
// "username": [
// "Account 'test2#test.com' already exist!"
// ]
// };
for(var field in errors) {
$scope.RegForm.$setValidity(field, false);
$scope.RegForm[field].$error.server = errors[field].join('\n');
}
The problem is that errors remain visible even when I change field. I need to set validity back to true, and remove server error at some moment. Just not sure how and when.
How to properly treat server data in order to make them change? $asyncValidators will not work, because in the case of username field I'm not allowed to register user, just to see if such username is free.
Based on the responses suggested in AngularJS - Server side validation and client side forms, we create a directive that will reset the validation after changing the properties of the model.
Live example on jsfiddle.
angular.module('ExampleApp', ['ngMessages'])
.controller('ExampleController', function($scope, ServerService) {
$scope.user = {};
$scope.doSubmit = function(myForm) {
ServerService.save().then(function(errors) {
// Set error from server on our form
angular.forEach(errors, function(error) {
myForm[error.fieldName].$setValidity(error.error, false);
});
});
};
})
//Simulate Ajax call
.service('ServerService', function($q, $timeout) {
var errorsFromServer = [{
fieldName: "firstName",
error: "serverError"
}, {
fieldName: "lastName",
error: "serverError"
}, {
fieldName: "email",
error: "serverError"
}, {
fieldName: "email",
error: "serverError2"
}];
return {
save: function() {
return $q.when(errorsFromServer);
}
};
})
.directive('serverValidation', function() {
return {
restrict: "A",
require: "ngModel",
scope: {
ngModel: "=",
serverValidation: "=" // String or array of strings with name of errors
},
link: function(scope, elem, attr, ngModelCtrl) {
function setValidity(errorName) {
console.log(errorName);
ngModelCtrl.$setValidity(errorName, true);
}
if (typeof(scope.serverValidation) == "string") {
console.log(scope.serverValidation);
scope.arrServerValidation = [scope.serverValidation];
} else {
scope.arrServerValidation = scope.serverValidation;
}
var firstError = scope.arrServerValidation[0];
scope.$watch('ngModel', function() {
// workaround to don't update $setValidity, then changed value of ngModel
// update $setValidity, only when server-error is true
if (firstError && ngModelCtrl.$error[firstError])
angular.forEach(scope.arrServerValidation, setValidity);
});
},
};
});
.error {
color: red;
font-style: italic;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-messages.min.js"></script>
<div ng-app="ExampleApp">
<div ng-controller="ExampleController">
<ng-form name="myForm">
<input ng-model="user.firstName" name="firstName" required server-validation="'serverError'">
<div ng-messages="myForm.firstName.$error" class="error">
<div ng-message="required">firstName is required</div>
<div ng-message="serverError">firstName is server error</div>
</div>
<input ng-model="user.lastName" name="lastName" required server-validation="'serverError'">
<div ng-messages="myForm.lastName.$error" class="error">
<div ng-message="required">lastName is required</div>
<div ng-message="serverError">lastName is server error</div>
</div>
<input ng-model="user.email" name="email" required server-validation="['serverError','serverError2']">
<div ng-messages="myForm.email.$error" class="error" multiple="true">
<div ng-message="required">email is required</div>
<div ng-message="serverError">email is server error</div>
<div ng-message="serverError2">email is server error 2</div>
</div>
<input ng-disabled="myForm.$invalid" ng-click="doSubmit(myForm)" type="submit">
</ng-form>
</div>
</div>
Try like this
for (var field in errors) {
$timeout(function(){
$scope.RegForm.$setValidity(field, false);
$scope.RegForm[field].$error.server = errors[field].join('\n');
});
}

ng-pattern for the same password and confirm password

app.directive("pwCheck", function () {
alert('hey');
return {
require: 'ngModel',
link: function (scope, elem, attrs, ctrl) {
var firstPassword = '#' + attrs.pwCheck;
elem.add(firstPassword).on('keyup', function () {
scope.$apply(function () {
// console.info(elem.val() === $(firstPassword).val());
ctrl.$setValidity('pwmatch', elem.val() === $(firstPassword).val());
});
});
}
}
});
I have a signup form and users must to input a password and a confirm password, My problem is my directive is not working in validating the password in confirm password to check if the password are the same, Is there another way to implement this validation like ng-pattern to check if the inputs are the same?Thanks in advance.
A better approach is to the $validators pipeline. However, this is also implemented in a form of a directive. This feature was introduced in Angular 1.3. The old fashion way was to use $parsers and / or $formatters. The huge advantage of the validators pipeline is that you have access to both, viewValue and modelValue. Just pass the password to the directive and add a new validator.
var app = angular.module('myApp', []);
app.controller('TestCtrl', TestController);
function TestController() {
var vm = this;
vm.password = '';
vm.confirmPassword = '';
}
app.directive('confirmPassword', ConfirmPassword);
function ConfirmPassword() {
var linkFn = function(scope, element, attributes, ngModel) {
ngModel.$validators.confirmPassword = function(modelValue) {
return modelValue == scope.password;
};
scope.$watch("password", function() {
ngModel.$validate();
});
};
return {
require: "ngModel",
scope: {
password: "=confirmPassword"
},
link: linkFn
};
};
input {
display: block;
margin-top: 5px;
margin-bottom: 10px;
}
.error {
color: red;
font-weight: bold;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<div ng-app="myApp" ng-controller="TestCtrl as testCtrl">
<form name="myForm">
<label for="password">Password</label>
<input id="password" name="password" ng-model="testCtrl.password" type="password" />
<label for="confirmPassword">Confirm Password</label>
<input id="confirmPassword" name="confirmPassword" ng-model="testCtrl.confirmPassword" data-confirm-password="testCtrl.password" type="password" />
<span class="error" ng-show="myForm.confirmPassword.$invalid && !myForm.confirmPassword.$pristine">Passwords do not match!</span>
</form>
</div>
This will check if both passwords are matching. If the passwords do not match the validity will be false. In this case I display an error message.

Reset Validity of ngModel

Recently, I had a problem with Angular form validity. I easy can to add the entry to Array with help ngModel.$setValidity, but I can't to remove it after. My html tag has a lot of valid/invalid classes. I'd tried to do the form to pristine. But it does't work. How that things to do generally? Help me please! (Sorry for my english =) if I've made a mistake somewhere.)
It's not terribly well documented, but you can actually pass in null to the $setValidity() function in order to completely clear a validation flag.
If you want to set it to be valid then simply pass in true
//Reset validity for this control
this.form.firstName.$setValidity('someValidator', null);
//Or set to valid
this.form.firstName.$setValidity('someValidator', true);
And here is a running snippet to demonstrate this technique.
(function() {
'use strict';
function MainCtrl() {
this.firstName = 'Josh';
}
MainCtrl.prototype = {
setInvalid: function(ctrl) {
ctrl.$setValidity('customValidator', false);
},
setPristine: function(ctrl) {
ctrl.$setValidity('customValidator', null);
},
};
angular.module('sample', [])
.controller('MainCtrl', MainCtrl);
}());
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet" />
<div class="container" ng-app="sample" ng-controller="MainCtrl as ctrl">
<div class="row">
<div class="col-sm-12">
<form name="ctrl.form">
<div class="form-group" ng-class="{'has-error':ctrl.form.firstName.$invalid}">
<label class="control-label">First Name</label>
<input type="text" class="form-control" name="firstName" ng-model="ctrl.firstName" />
</div>
<button type="button" class="btn btn-danger" ng-click="ctrl.setInvalid(ctrl.form.firstName)">Set Invalid</button>
<button type="button" class="btn btn-success" ng-click="ctrl.setPristine(ctrl.form.firstName)">Set Valid</button>
</form>
</div>
</div>
</div>
(function () {
angular.module("App")
.directive("password", password);
function password() {
var lastTrueRegex = {};
var regexes = {
week: /(?=^.{8,}$).*$/,
pettyWeek: /(?=^.{8,}$)(?=.*\d).*$/,
normal: /(?=^.{8,}$)(?=.*\d)(?=.*[a-z]).*$/,
prettyStrong: /(?=^.{8,}$)(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).*$/,
strong: /(?=^.{8,}$)(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?![.\n]).*$/,
veryStrong: /(?=^.{8,}$)(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?![.\n])(?=.*[!##$%^&*]+).*$/
};
function forEach(data, callback) {
for (var key in data) {
if (data.hasOwnProperty(key)) {
callback(key, data[key]);
}
}
}
return {
require: "ngModel",
restrict: 'EA',
link: function (scope, element, attributes, ngModel) {
ngModel.$parsers.unshift(function (textValue) {
forEach(regexes, function (key, regex) {
if (regex.test(textValue)){
lastTrueRegex.name = key;
lastTrueRegex.value = true;
}else{
ngModel.$setValidity(key, null);
}
});
if (lastTrueRegex.name){
ngModel.$setValidity(lastTrueRegex.name, lastTrueRegex.value);
return textValue;
}
});
ngModel.$formatters.unshift(function (modelValue) {
//ngModel.$setValidity('strongPass', isValid(modelValue));
return modelValue;
});
}
};
}
})();
<form name="myForm">
<input type="text" name="password" ng-model="textValue" password/>
</form>

Resources