How to asynchronously validate form in Angular after submit? - angularjs

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');
});
}

Related

How to use Material Design Lite and Angular for forms

*Disclaimer: this question is not about using material design in an angular app but using material design lite inside a form. So, please, don't answer I should rather use angular material, materialize, lumx, material bootstrap, or daemonite... I know, they exist.*
With Angular a typical form field for a name would be:
<form name="myForm">
<label>
Enter your name:
<input type="text"
name="myName"
ng-model="name"
ng-minlength="5"
ng-maxlength="20"
required />
</label>
<div ng-messages="myForm.myName.$error" style="color:maroon" role="alert">
<div ng-message="required">You did not enter a field</div>
<div ng-message="minlength">Your field is too short</div>
<div ng-message="maxlength">Your field is too long</div>
</div>
</form>
With Material Design Lite, it would be something like that:
<form action="#">
<div class="mdl-textfield mdl-js-textfield">
<input class="mdl-textfield__input" type="text" id="user" pattern="[A-Z,a-z, ]*" />
<label class="mdl-textfield__label" for="user">User name</label>
<span class="mdl-textfield__error">Letters and spaces only</span>
</div>
</form>
Question: how is it possible to use the angular validation functionality combined with ngMessage (for multiple error messages) with the Material Design Lite?
You can write your own angular module to validate MDL input fields, here is a working example: http://codepen.io/alisterlf/pen/ZGgJQB
JS
// create angular app
var validationApp = angular.module('validationApp', ['fieldMatch']);
//Field Match directive
angular.module('fieldMatch', [])
.directive('fieldMatch', ["$parse", function($parse) {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ctrl) {
var me = $parse(attrs.ngModel);
var matchTo = $parse(attrs.fieldMatch);
scope.$watchGroup([me, matchTo], function(newValues, oldValues) {
ctrl.$setValidity('fieldmatch', me(scope) === matchTo(scope));
}, true);
}
}
}]);
//Run material design lite
validationApp.run(function($rootScope, $timeout) {
$rootScope.$on('$viewContentLoaded', function(event) {
$timeout(function() {
componentHandler.upgradeAllRegistered();
}, 0);
});
$rootScope.render = {
header: true,
aside: true
}
});
// create angular controller
validationApp.controller('mainController', function($scope) {
$scope.formStatus = '';
// function to submit the form after all validation has occurred
$scope.submit = function() {
// check to make sure the form is completely valid
if ($scope.form.$invalid) {
angular.forEach($scope.form.$error, function(field) {
angular.forEach(field, function(errorField) {
errorField.$setTouched();
})
});
$scope.formStatus = "Form is invalid.";
console.log("Form is invalid.");
} else {
$scope.formStatus = "Form is valid.";
console.log("Form is valid.");
console.log($scope.data);
}
};
});

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

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.

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>

Validation using custom directive with angularjs

I have a form with input fields and have a custom directive for each input field to validate the data entered by user.Requirement is that when user leaves the input field with invalid data, error message to be displayed.However, few of the fields are optional that if User skips with out entering any data for these fields, no Validation is required.
I tried to implement this using Blur event in the directive, but this is causing the vaidation called even in case of no data entered.
Please advise if Watch function can be used here and any sample snippet here would be appreciated. I have written code in a fiddle which is similar to my directive for one of the input field that checks minimum length (although there is a built-in directive, taken this as an example).This fiddle can be accessed at http://jsfiddle.net/4xbv0tgL/49/
<div ng-app="myApp" ng-controller="myCtrl">
<form name="myForm">
Num1: <input type="text" ng-model="num1" my-min-length="5" name="num1" />
<span class="error" ng-hide="myForm.num1.$valid"
ng-show="myForm.num1.$error">Invalid Number!</span>
<br />
Num2: <input type="text" ng-model="num2" my-min-length="5" name="num2" />
<span class="error" ng-hide="myForm.num2.$valid"
ng-show="myForm.num2.$error">Invalid Number!</span>
</form>
</div>
var myApp = angular.module("myApp", []);
var myCtrl = myApp.controller("myCtrl",["$scope", function($scope) {
$scope.num1 = "12345";
$scope.num2 = "55555";
}]);
myApp.directive("myMinLength", function () {
return {
restrict: "A",
require: "ngModel",
link: function (scope, element, attr, ngModelCtrl) {
var minlength = Number(attr.myMinLength);
var inputparse = function (inputtext) {
if ((inputtext.length >= minlength) && (!isNaN(inputtext))) {
return inputtext;
}
return undefined;
}
ngModelCtrl.$parsers.push(inputparse);
element.bind("blur", function () {
var value = inputparse(element.val());
var isValid = !!value;
if (isValid) {
ngModelCtrl.$setViewValue(value);
ngModelCtrl.$render();
}
ngModelCtrl.$setValidity("myMinLength", isValid);
scope.$apply();
}
);
}
};
});
I think you are over complicating life for yourself. Why don't you just use multiple directives for multiple checks?
<div ng-app="myApp"
ng-controller="myCtrl">
<form name="myForm"
novalidate>
Num1:
<input type="text"
ng-model="num1"
ng-minlength="5"
integer
name="num1"
required/>
<span class="error"
ng-show="myForm.num1.$invalid&&myForm.num1.$touched">Invalid Number!</span>
</form>
</div>
And here's the integer directive:
var INTEGER_REGEXP = /^[0-9]*$/;
myApp.directive('integer', function () {
return {
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
ctrl.$parsers.unshift(function (viewValue) {
if (viewValue === "") {
ctrl.$setValidity('integer', true);
return viewValue;
} else if (INTEGER_REGEXP.test(viewValue)) {
ctrl.$setValidity('integer', true);
return viewValue;
} else {
ctrl.$setValidity('integer', false);
return viewValue;
}
});
}
};
});
Just be sure that you are using angular 1.3 or a newer version. Because $touched and $untouched don't exist in 1.2.

AngularJS : ngShow triggers double in controller

I have a problem with the controller triggering the method defined on the $scope twice when the page first loads and when the form submits.
Here's the fiddle : http://jsfiddle.net/scabro/pQb6q/5/
<div data-ng-app="app" data-ng-controller="AppCtrl">
<form method="post" data-ng-submit="submitForm()">
<span data-ng-show="showWarning('email')">Please provide valid email address</span>
<input type="email" name="email" placeholder="Email address" data-ng-model="fields.email.value" />
<span data-ng-show="showWarning('telephone')">Please provide your telephone number</span>
<input type="text" name="telephone" placeholder="Telephone" data-ng-model="fields.telephone.value" />
<span data-ng-show="showWarning('name')">Please provide your name</span>
<input type="text" name="name" placeholder="Name" data-ng-model="fields.name.value" />
<button type="submit">Submit</button>
</form>
</div>
And here are two AngularJs modules - first with with controller and the other with 'isValid' factory service
angular.module('app', ['validation'])
.controller('AppCtrl', function($scope, $log, isValid) {
$scope.fields = {
email : {
type : 'email',
value : '',
required : true
},
telephone : {
type : 'number',
value : '',
required : true
},
name : {
type : 'string',
value : '',
required : true
}
};
$scope.warnings = [];
$scope.showWarning = function(field) {
$log.info($scope.warnings);
return ($scope.warnings.indexOf(field) !== -1);
};
$scope.submitForm = function() {
$scope.warnings = [];
isValid.run($scope.fields);
if (isValid.errors.length > 0) {
$scope.warnings = isValid.errors;
}
};
});
angular.module('validation', [])
.factory('isValid', function() {
return {
errors : [],
isEmail : function(emailValue) {
return (
emailValue.indexOf("#") != -1
);
},
isNumber : function(numberValue) {
return (
!isNaN(parseFloat(numberValue)) &&
isFinite(numberValue)
);
},
isEmpty : function(emptyValue) {
return (
emptyValue === '' ||
emptyValue === 'undefined'
);
},
validateEmail : function(fieldObject, fieldName) {
if (!this.isEmail(fieldObject.value)) {
this.errors.push(fieldName);
}
},
validateNumber : function(fieldObject, fieldName) {
if (!this.isNumber(fieldObject.value)) {
this.errors.push(fieldName);
}
},
validateEmpty : function(fieldObject, fieldName) {
if (this.isEmpty(fieldObject.value)) {
this.errors.push(fieldName);
}
},
type : function(fieldObject, fieldName) {
switch(fieldObject.type) {
case 'email':
if (fieldObject.required || !this.isEmpty(fieldObject.value)) {
this.validateEmail(fieldObject, fieldName);
}
break;
case 'number':
if (fieldObject.required || !this.isEmpty(fieldObject.value)) {
this.validateNumber(fieldObject, fieldName);
}
break;
default:
if (fieldObject.required) {
this.validateEmpty(fieldObject, fieldName);
}
break;
}
},
resetErrors : function() {
this.errors = [];
},
run : function(fields) {
this.resetErrors();
for (var fieldName in fields) {
if (fields.hasOwnProperty(fieldName)) {
this.type(fields[fieldName], fieldName);
}
}
}
};
});
Also - for some reason the validation only work for the first 2 fields and doesn't seem to validate an empty field.
Here's what I'm getting in the console when page first loads (empty arrays) and when the form is first submitted:
it also seem to be calling the showWarning() method with each 'keydown' event when typing inside of any of the fields.
Any idea what might be causing it?
validateEmpty has a spurious ! before this.isEmpty(....
As for why they're getting called twice, I'd guess a digest cycle is running when you submit, and Angular is checking its watches to decide whether to keep showing those spans, but I'm not entirely sure why it's doing that in this case.
You are not using the angular standard validation.
here is an example with a very crud telephone custom validation
<div data-ng-app="app" data-ng-controller="AppCtrl">
<form method="post" name="form" data-ng-submit="submitForm()">
<input required="" type="email" name="email" placeholder="Email address" data-ng-model="fields.email.value" />
<div data-ng-show="form.email.$dirty && form.email.$invalid">Please provide valid email address</div>
<br />
<input required="" valid-telephone type="text" name="telephone" placeholder="Telephone" data-ng-model="fields.telephone.value" />
<div data-ng-show="form.telephone.$dirty && form.telephone.$invalid">Please provide your telephone number</div>
<br />
<input required="" type="text" name="name" placeholder="Name" data-ng-model="fields.name.value" />
<div data-ng-show="form.name.$dirty && form.name.$invalid">Please provide your name</div>
<br />
<button type="submit">Submit</button>
</form>
</div>
angular.module('app', [])
.controller('AppCtrl', function($scope, $log) {
$scope.fields = {
email : {
value : ''
},
telephone : {
value : ''
},
name : {
value : ''
}
};
$scope.submitForm = function() {
};
})
.directive('validTelephone', [function() {
return {
require: 'ngModel',
link: function(scope, ele, attrs, c) {
scope.$watch(attrs.ngModel, function() {
c.$setValidity('unique', /^\d{10}$/.test(c.$viewValue));
});
}
};
}]);
input.ng-invalid {
border: 1px solid red;
}
input.ng-valid {
border: 1px solid green;
}
Here is the code
http://plnkr.co/edit/Y9DzmBNlu9wNiJ1Odljc?p=preview
here is some documentation
http://scotch.io/tutorials/javascript/angularjs-form-validation

Resources