Custom validation with AngularJS isn't triggered - javascript

I'm a beginner with AngularJS and for now I'm trying to create a registration form.
I tried to follow the instructions I found on the internet, but it looks like I cannot get my validation triggered. I'm not sure what is wrong.
Here's my JS for the validation:
var login = angular.module('login', []);
login.directive('repeatedValue', function () {
return {
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
ctrl.$validators.repeatedValue = function (modelValue, viewValue) {
alert("validation");
return false;
};
}
};
});
and the input field looks like this:
<input type="password" class="form-control" id="password2"
placeholder="Repeat password" value="" tabindex="3" ng-model="login.registerPassword2" repeatedValue/>
still, for some reason, even if I start typing into the field, the validation is not triggered and as a result I can still submit the form.

you must use right directive name in template (dash-delimited)
<input type="password" ... repeated-value/>
See Normalization chapter in docs https://docs.angularjs.org/guide/directive

Related

Using angular form in directive template

I am using require: '^form' in my simple directive.
Then I trying to use this form in an ng-show but it doesn't seem to work.
Note: I don't want to pass the form name in as an attribute.
Can anyone see where i am going wrong? I only want the message to show when the form is invalid.
angular.module('xxx').directive('errorWall', errorWall);
function errorWall() {
return {
restrict: 'E',
require: '^form',
scope: {},
link: (scope, elm, attrs, frm) => {
scope.formCtrl = frm;
},
template: '<div ng-show="formCtrl.$invalid">You have error messages.</div>'
};
}
Make sure you've placed the directive inside the form with at least one input with a ng-model directive on it.
<form>
<input type="text" ng-model="name" required />
<error-wall></error-wall>
</form>
Here's a working fiddle https://jsfiddle.net/3gv8nvL3/3/ with one form required input.

Using a service to update the focused elements ng-class on ng-change

I would like to make my service be able to handle any input field. At the moment I am manually writing everything and it's starting to amount to a lot of manual work. Is there a way to send the element object when the elements ng-change property is invoked? I can then change the elements ng-class as a result of that.
Html:
<input type="text" id="email" data-ng-model="email" data-ng-change="changeEmail()" placeholder="your email here" data-ng-class="emailFormControlColor">
In the controller:
$scope.changeEmail = function () {
if ($checkInput.checkEmail($scope.email)) {
// email input is good
$scope.emailFormControlColor = 'form-control-success'; // change from error to success
} else {
// email input is bad
if ($scope.emailFormControlColor === 'form-control-success')
$scope.emailFormControlColor = 'form-control-error'; // change from success to error
}
};
The service (this is included in the controller arguments ofc.):
.service('checkInput', ['$controller', '$window', '$location', function ($controller, $window, $location) {
return {
checkEmail: function (email) {
// <--- I would like to update the ng-class of the focused element here! This would result in me not manually having to write code for each input!
var regex = /^[^\s#]+#[^\s#]+\.[^\s#]+$/;
return regex.test(email);
}
};
}])
The code above is how i have it now. As you can see I'm manually changing the $scope.emailFormControlColor.
Imagine I have three input fields:
<input type="text" id="email1" data-ng-model="email1" data-ng-change="changeEmail()" placeholder="your email here" data-ng-class="emailFormControlColor1">
<input type="text" id="email2" data-ng-model="email2" data-ng-change="changeEmail()" placeholder="your email here" data-ng-class="emailFormControlColor2">
<input type="text" id="email3" data-ng-model="email3" data-ng-change="changeEmail()" placeholder="your email here" data-ng-class="emailFormControlColor3">
How could I write my service so that I do not have the write the following manually:
$scope.emailFormControlColor1 = 'form-control-success';
$scope.emailFormControlColor2 = 'form-control-success';
$scope.emailFormControlColor3 = 'form-control-success';
I hope my question is clear, otherwise say and I'll update it!
I think using a directive to solve this problem is more desirable. For one, it's considered an anti-pattern to modify the DOM in a controller or a service.
The other reason is that when you use a directive, you will be provided the DOM element that the directive was used on. Here's some untested code that you might use as a starting point:
myModule.directive('checkEmail', function() {
require: 'ngModel',
link: function(scope, element, attributes, ngModelController) {
// require that ng-model is used on this element, so you can hook
// into the Angular validator pipeline ...
ngModelController.validators.checkEmail = function(modelValue, viewValue) {
// now return true or false if viewValue is considered valid
var regex = /^[^\s#]+#[^\s#]+\.[^\s#]+$/;
var isValid = regex.test(viewValue);
// but you also have an opportunity to do your DOM manipulation
element.toggleClass('form-control-success', isValid);
element.toggleClass('form-control-error', !isValid);
return isValid;
});
}
});
Instead of applying the CSS class as above, you could just use Angular's built in validation system. Your directive code is the same as above, except you don't apply any CSS classes in the directive. When the validation code in the directive returns false, Angular will make the field invalid ... and you can use this fact to apply the CSS in your HTML:
<form name="myForm">
<input ng-model="whatever"
name="email"
check-email
ng-class="{'form-control-success': myForm.email.$valid, 'form-control-error': myForm.email.$invalid}">
</form>
The above applies the CSS anytime the field is invalid, but you can also apply it when a specific validation error occurs, in this cased we named the validator "checkEmail", so this also would work:
<form name="myForm">
<input ng-model="whatever"
name="email"
check-email
ng-class="{'form-control-success': !myForm.email.$error.checkEmail, 'form-control-error': myForm.email.$error.checkEmail}">
</form>
I would create an object that contains all of the information you need for each email. Something like this:
HTML:
<input type="text" id="email0" data-ng-model="emails[0].value" data-ng-change="changeEmail(emails[0])" placeholder="your email here" data-ng-class="emails[0].class">
<input type="text" id="email1" data-ng-model="emails[1].value" data-ng-change="changeEmail(emails[1])" placeholder="your email here" data-ng-class="emails[1].class">
<input type="text" id="email2" data-ng-model="emails[2].value" data-ng-change="changeEmail(emails[2])" placeholder="your email here" data-ng-class="emails[2].class">
JavaScript:
$scope.emails = [
{ value: '', class: '' },
{ value: '', class: '' },
{ value: '', class: '' },
];
$scope.changeEmail = function (email) {
if ($checkInput.checkEmail(email)) {
// email input is good
email.class = 'form-control-success'; // change from error to success
} else {
// email input is bad
if (email.class === 'form-control-success') {
email.class = 'form-control-error'; // change from success to error
}
}
};
If appropriate, you could use something like ng-repeat to avoid copy/paste in the HTML.
Even though Sunil D. pointed me basically to the answer the code is incorrect. Down below is the code that makes it work. http://codepen.io/basickarl/pen/MyoZNB
HTML:
<div ng-app="app" ng-controller="ctrl">
<form name="myForm">
<input ng-model="name" name="name">
<input ng-model="email" name="email" check-email>
</form>
</div>
CSS:
input {
border: 5px;
border-style: solid;
border-color: silver;
}
.input-invalid {
border-color: red;
}
.input-valid {
border-color: lime;
}
JS:
var app = angular.module('app', []);
app.controller('ctrl', ['$scope', function($scope) {
$scope.name = "";
$scope.email = "";
}]);
app.directive('checkEmail', [function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, el, attr, ctrl) {
ctrl.$validators.checkEmail = function(modelVal, viewVal) {
var regex = /^[^\s#]+#[^\s#]+\.[^\s#]+$/;
var isValid = regex.test(viewVal);
el.toggleClass('input-valid', isValid);
el.toggleClass('input-invalid', !isValid);
return isValid;
}
}
}
}]);

Angular form validation - hide errors on field update

I have a form with currently one field, which has few validation rules:
<form name="my_form" novalidate ng-controller="FormController">
<label>Your Name:</label>
<input type="text"
name="name"
placeholder="Your Name"
ng-model="form.name"
ng-minlength="3"
ng-maxlength="20"
unique
required />
<button ng-click="submitForm()">Submit</button>
<div class="error"
ng-show="my_form.isSubmitted"
ng-messages="my_form.name.$error">
<div ng-messages-include="errors.html"></div>
</div>
</form>
My field is validated against:
Min. length;
Max. length;
It's required
And must be unique (custom validation rule)
I'm using ng-messages to display error messages near input field. Here is my errors.html template:
<div ng-message="required">This field is required.</div>
<div ng-message="minlength">This field is too short.</div>
<div ng-message="maxlength">This field is too long.</div>
<div ng-message="unique">The value of this field must be unique.</div>
The validation should be started only after 'Submit' button is pressed (submitForm() function sets my_form.isSubmitted flag to true and my error div is displayed)
Here is my js code:
var app = angular.module('formValidation', ['ngMessages']);
app.controller('FormController', function($scope) {
$scope.submitForm = function() {
$scope.my_form.isSubmitted = true;
};
});
app.directive('unique', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, ele, attrs, ctrl) {
var names = ['Dmitry', 'Alexander', 'Elizabeth'];
ctrl.$parsers.push(function(value) {
if (names.indexOf(value) > -1) {
ctrl.$setValidity('unique', false);
return false;
}
ctrl.$setValidity('unique', true);
return true;
});
}
};
);
Everything works fine, but what I want to do now is to hide errors if the field is modified after errors were shown (until submit button will be pressed again).
The first idea came to my mind is to add another condition to ng-show directive of error div to check if corresponding field is updated and if it is, errors should not be shown. Something like:
<div class="error"
ng-show="!my_form.name.isUpdated && my_form.isSubmitted"
ng-messages="my_form.name.$error">
<div ng-messages-include="errors.html"></div>
</div>
So, on button click I can set isUpdated flag of all form fields to false and on input update can set it to true. But this solution seems to me far from elegant. I'm sure there is a better way to achieve this behaviour. Any ideas?
My current solution (probably not the best one):
<input type="text"
name="name"
placeholder="Your Name"
ng-model="form.name"
ng-minlength="3"
ng-maxlength="20"
unique
updatable
required />
<button ng-click="submitForm()">Submit</button>
<div class="error"
ng-show="!my_form.name.isDirty && my_form.isSubmitted"
ng-messages="my_form.name.$error">
<div ng-messages-include="errors.html"></div>
</div>
I added new directive updatable to my field and updated the show condition for error div:
ng-show="!my_form.name.isDirty && my_form.isSubmitted"
The directive:
app.directive('updatable', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, ele, attrs, ctrl) {
ele.bind('input', function() {
scope.$apply(function() {
ctrl.isDirty = true;
});
);
}
};
});
And a small update of submitForm function which now sets isDirty flag of my field(s) to false:
$scope.submitForm = function() {
$scope.my_form.isSubmitted = true;
$scope.my_form.name.isDirty = false;
};

Angular: directive access to form element

I have a form label containing an input:
<label data-live-email-check="http://www.example-service-uri.com/">
<span class="embedded-label">Email</span>
<input ng-model="formData.email"
type="email"
name="email"
placeholder="Email"
required/>
<span class="message" ng-show="emailValidationMessage">{{emailValidationMessage}}</span>
</label>
I want to create a directive that takes the URL provided by the data-live-email-check attribute and sends the email to that URL, validating whether or not it already exists.
angular.module("App").directive("liveEmailCheck", [function () {
return {
restrict: "A",
scope: {
ngModel: "="
},
link: function (scope, element, attrs) {
scope.$watch(
function(){
return scope.ngModel
},
function(newVal){
console.log(newVal);
}
);
}
}
}]);
I just want to watch the model on the input, and fire a request when it updates.
Since the directive is defined on the label element ngModel is not properly bound. What am I doing wrong? My watch expression is not logging anything because it never fires.
I know I could manually grab the input, but that just seems like I'd be breaking the "angular pattern" that I feel so "bound" by. The frustrating thing is that there seems to be so many ways to do everything in Angular that I can never tell if I'm approaching a problem correctly or not.
--Edit--
To provide the solution that I personally would take (out of ignorance of a "better" way), would be the following:
angular.module("App").directive("liveEmailCheck", [function () {
return {
restrict: "A",
require: ["^form"],
link: function (scope, element, attrs, ctrl) {
var formCtrl = ctrl[0];
scope.formEl = formCtrl[element.find("input").attr("name")];
scope.$watch(function(){return scope.formEl.$valid},
function(newVal){
console.log(newVal);
});
}
}
}]);
This WORKS, but I feel like it "breaks the angular rules".
A custom validation is written like this:
'use strict';
angular.module('App')
.directive('liveEmailCheck', function (){
return {
require: 'ngModel',
link: function (scope, elem, attr, ngModel){
ngModel.$validators.liveEmailCheck= function (value){
//Your logic here or in a factory
};
}
};
});
and then on your HTML it goes like
<input type="email" live-email-check="liveEmailCheck>
Basically you add your own validation to the set of build-in validations of angular.
What you need here is an ng-model asyncValidator. Here is a simple implementation of such a directive.
angular.module("App").directive('asyncEmailValidator', function ($http) {
return {
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
var emailValidationUrl = attrs.asyncEmailValidator;
ngModel.$asyncValidators.emailValidator = function (modelValue, viewValue) {
var value = modelValue || viewValue;
// NOTE: don't forget to correctly add the value to the url
return $http(emailValidationUrl + value).then(function (validationResponse) {
// NOTE: return rejected promise if validation failed else true
});
};
}
};
});
How you can use it in your case:
<label>
<span class="embedded-label">Email</span>
<input ng-model="formData.email"
async-email-validator="http://www.example-service-uri.com/"
type="email"
name="email"
placeholder="Email"
required/>
<span class="message" ng-show="<FormNameHere>.email.$error.emailValidator">
{{emailValidationMessage}}
</span>
</label>
This will be the right solution because it is implemented with angular ng-model validation which considers the validity of the model too.

How to invalidate a form when using directive in AngularJS

i am working on a SPA and a form inside this app uses an input masked text box implemented using a third party library from here
i created a directive to set a mask for an IP address
angular
.module('app').directive('ipMask', [
function () {
return {
restrict: 'A',
require: '?ngModel',
link: function (scope, element, attrs, ngModel) {
element.mask('0ZZ.0ZZ.0ZZ.0ZZ', {translation: {'Z': {pattern: /[0-9]/, optional: true}}});
element.mask('099.099.099.099');
scope.$watch(attrs.ngModel, function (newValue, oldValue) {
//????????
});
}
};
}
]);
where my form code looks like
<div ng-controller="nodesListCtrl as vm">
<form role="form" name="frmNode">
<div class="form-group">
<label>Node IP :</label>
<input type="text" data-ip-mask ng-model="vm.myIp" name="CtrIp" class="input-sm form-control" placeholder="..." >
</div>
</form>
</div>
i want to invalidate the form if the IP address is wrong. i.e. i am expecting .ng-invalid class both on the form and and the control as well until the time it remains invalid. any suggestions ?
You don't need to use $watch. Just add a parser and/or formatter. Parsers are called when the view changes and formatters when the model changes. (This link can tell you more about those, as well as the other things available on ngModelController: https://docs.angularjs.org/api/ng/type/ngModel.NgModelController). Here's an example:
link: function (scope, element, attrs, ngModel) {
element.mask('0ZZ.0ZZ.0ZZ.0ZZ', {translation: {'Z': {pattern: /[0-9]/, optional: true}}});
element.mask('099.099.099.099');
ngModel.$parsers.unshift(function(value) {
var valid = isValid(value); // made up - use whatever validation technique based on that library
ngModel.$setValidity('ip', valid);
return valid;
});
ngModel.$formatters.unshift(function(value) {
var valid = isValid(value);
ngModel.$setValidity('ip', valid);
return valid ? value : undefined;
});
}

Categories