Custom angularjs validator does not have up-to-date view - angularjs

In angularjs 1.3.15 I have a custom validator which uses its attribute value:
sApp.directive('customValidator', [ '$q', function($q){
return {
require: 'ngModel',
link : function(scope, elm, attrs, ctrl){
ctrl.$asyncValidators.customValidator= function(modelValue)
{
// I need the value of "attrs.customValidator"
};
}
};
}]);
I use this validator in the following way:
<form ... ng-show="data">
<input ... custom-validator='{{data}}'/>
</form>
data is some data being asynchronously loaded by a web-service. Unfortunately when the validator is called the attribute value is not available yet (despite ng-show="data"). So i call scope.$apply() inside the validator. This solves my problem but I get the error "$digest already in progress".
Does anybody know a better solution?

I think a synchronous formatter/parser will suffice. Here is a plunker
Here is the directive:
app.directive('exactlyEquals', function() {
return {
require: "ngModel",
link: function(scope, elm, attrs, ctrl) {
var validator = function(value) {
ctrl.$setValidity('exactlyEquals', value == attrs.exactlyEquals);
return value;
};
ctrl.$parsers.unshift(validator);
ctrl.$formatters.unshift(validator);
}
};
});
And its usage:
<form name="theForm">
<input type="text" exactly-equals="test2" ng-model="data.name" />
</form>
{{ theForm.$valid }}

#user3632710 ng-if="data" did the job. My code looks now like this:
<form ng-controller="MyController">
<div ng-if="data"> <!-- ng-if cannot by on same DOM as controller -->
<input ... custom-validator='{{data}}'/>
</div>
</form>

Related

How to set input field validation in reusable custom directive?

I'm trying to make a reusable custom directive that will validate date in input field. Code provided below is working, however is not reusable at all which is my biggest concern.
What I was trying to do, was to set a new scope in directive however I got an error:
Multiple directives requesting isolated scope.
So I guess isolated scope is not going to help me.
Any other solutions?
That's my first template:
<form ng-submit="add()" name="addTask" class="form-horizontal">
<input name="dateInput" is-date-valid type="text" class="form-control" ng-model="task.DueDate" datepicker-options="datepicker.options" ng-model-options="{ timezone: 'UTC' }" uib-datepicker-popup="mediumDate" is-open="isOpened" required>
</form>
That's my second template:
<form ng-submit="edit()" name="editTask" class="form-horizontal">
<input name="dateInput" is-date-valid type="text" class="form-control" ng-model="task.DueDate" datepicker-options="datepicker.options" ng-model-options="{ timezone: 'UTC' }" uib-datepicker-popup="mediumDate" is-open="isOpened" required>
</form>
And that's my custom directive:
function isDateValid($log) {
'ngInject';
var directive = {
restrict: 'A',
require: 'ngModel',
link: link
};
return directive;
function link(scope, element, attrs, ctrl) {
scope.$watch(attrs.ngModel, function () {
var validation = can_i_get_this_from_controller ?
if (validation) {
ctrl.$setValidity('validation', true);
} else {
ctrl.$setValidity('validation', false);
}
});
}
}
module.exports = isDateValid;
The way you implemented the custom validator is not good, you should be doing something like this -
.directive('dateValidate', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, elem, attrs, ngModel) {
ngModel.$validators.dateValidate = function(modelValue) {
//Your logic here, return true if success else false
}
}
};
});
It can be used on both form paths, so no need of that logic here.
To know more about these this is one good resource

angular directive was only called once

I am using a directive to build a custom validator and it works fine. But, it was called only once! If my "roleItems" are updated, this directive was not called again! How can it be called every time when "roleItems" are updated?
Here are the markups. And "Not-empty" is my directive.
<form name="projectEditor">
<ul name="roles" ng-model="project.roleItems" not-empty>
<li ng-repeat="role in project.roleItems"><span>{{role.label}}</span> </li>
<span ng-show="projectEditor.roles.$error.notEmpty">At least one role!</span>
</ul>
</form>
This is my directive. It should check if the ng-model "roleItems" are empty.
angular.module("myApp", []).
directive('notEmpty', function () {
return {
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
ctrl.$validators.notEmpty = function (modelValue, viewValue) {
if(!modelValue.length){
return false;
}
return true;
};
}
};
});
Main purpose of validator is validate ngModel value of user input or model change, so it should be uset to checkbox/textara/input and etc. You cant validate ng-model of everything. Angular is enough intelligent to knows that ng-model makes no sens so he is just ignoring it .
I you wanna change only error message you can check it via .length property. If you wanna make whole form invalid , i suggest you to make custom directive , put it on , and then in validator of this directive check scope.number.length > 0
Basically just adjust your directive code to input element and hide it .... via css or type=hidden, but dont make ngModel="value" its not make sense because ng-model is expecting value which can be binded and overwriteen but project.roleItems is not bindable! so put ng-model="dummyModel" and actual items to another param ...
<input type="hidden" ng-model="dummyIgnoredModel" number="project.roleItems" check-empty>
angular.module("myApp", []).
directive('checkEmpty', function () {
return {
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
ctrl.$validators.push(function (modelValue, viewValue) {
if(!scope.number.length){
return false;
}
return true;
});
//now we must "touch" ngModel
scope.$watch(function()
{
return scope.number
}, function()
{
ctrl.$setViewValue(scope.number.length);
});
}
};
});

AngularJS: Model not updating when using directive OR model-options

I have the current section of html that is used to check a users password,
<div class="form-group">
<label for="auditName" class="col-lg-4 control-label">Current Password </label>
<div class="col-lg-8">
<input type="password" placeholder="Current Password"
name="currentPassword"
class="form-control"
ng-model="currentPassword"
required=""
password-new
ng-model-options="{ updateOn: 'blur' }">
</div>
<div class="col-lg-offset-4" ng-if="form.$pending.oldPassword">checking....</div>
<div class="col-lg-offset-4" ng-if="form.$error.oldPassword">Please create a NEW password</div>
</div>
{{currentPassword}}
My issue is that the currentPassword is not being updated, so nothing is being displayed on the screen. If I remove the model-options AND I remove the reference to the new-password directive it will display as you type - so both of these are for some reason stopping the model from updating the value.
The directive new-password looks like this, and is still in a basic format I found elsewhere until I get this working properly,
app.directive('passwordNew', function ($timeout, $q) {
return {
restrict: 'AE',
require: 'ngModel',
link: function (scope, elm, attr, model) {
model.$asyncValidators.oldPassword = function () {
//here you should access the backend, to check if username exists
//and return a promise
var defer = $q.defer();
$timeout(function () {
model.$setValidity('oldPassword', true);
defer.resolve;
}, 1000);
return defer.promise;
};
}
}
});
Any ideas?
The {{currentPassword}} in your HTML is outside the scope of the directive. You need to link the two scopes. Checkout "Isolating the Scope of a Directive" in https://docs.angularjs.org/guide/directive.
Put something like this on the directive
scope: {
currentPassword: '='
},
EXAMPLE
This is how I solve a similar problem
app.directive('availableEmail', [
'dataSvc', (data:otolane.direct.IDataService) => {
return {
require: 'ngModel',
restrict: 'A',
link: function (scope, elm, attrs, ctrl) {
ctrl.$parsers.unshift(function (viewValue) {
ctrl.$setValidity('availableEmail', true);
//only check the db if value is an email
if (viewValue.length > 3 && !ctrl.$error.email) {
data.account.checkEmail(viewValue)
.then(() => {
// data method resolves if email is available
ctrl.$setValidity('availableEmail', true);
})
.catch(() => {
//returns error if email is in use
ctrl.$setValidity('availableEmail', false);
});
}
return viewValue;
});
}
};
}
]);

How can I setValidate on an element in the form from a directive?

I have a directive that links to a textbox on the form, and I would like for this directive to set the 'required' error.
Here's a fiddle that shows what I'm trying to do
http://jsfiddle.net/scottieslg/7qLsj3rr/3/
Html:
<div ng-app='myApp' ng-controller='TestCtrl'>
<ng-form name='testForm'>
<input type='text' name='myInput' />
<div ng-messages="testForm.myInput.$error">
<div ng-message="required">Required</div>
</div>
<test-directive ng-model='testModel'></test-directive>
</ng-form>
</div>
Javascript:
var app = angular.module('myApp', ['ngMessages']);
app.controller('TestCtrl', function($scope) {
$scope.testModel = {}
});
app.directive('testDirective', function() {
return {
restrict: 'E',
require: 'ngModel',
template: '<div><button ng-click="setError()">Set Error</button></div>',
link: function(scope, element, attrs, ngModelCtrl) {
scope.setError = function() {
// How can I set .setValidate('require', true) on myInput from here??
}
}
}
});
If you want the test-directive to be able to control the ngModelController instance on a separate named input in a form, then using the ng-model directive again isn't the right thing to do, as that would create a new ngModelController instance on test-directive.
What the test-directive actually needs to know is the name of the input which has the controller:
<test-directive name='myInput'></test-directive>
Then it can access the form controller, using
require: '^form',
and use the name attribute value to find the ngModelController instance on the form:
link: function(scope, element, attrs, formController) {
scope.setError = function() {
var ngModelCtrl = formController[attrs.name];
ngModelCtrl.$setValidity('required', false);
}
}
You can see this at http://jsfiddle.net/7qLsj3rr/6/ .
Note: if you're using required as the key, then as soon as you type in the input again again, angular's own required validation will kick in an remove the error.

How to access Angular form validation within a directive

I have a simple form with a directive:
<form name="testform">
<input name="testfield" type="text" ng-model="test.name" ng-minlength="3" my-validate>
</form>
Since Angular validates the field itself when entering less than 3 characters, it sets testform.testfield.$invalid.
What I want to achieve is to add some custom validation functionality in a directive and react to this angular validation/set the validation result e.g. with the help of watchers or events like so:
angular.module('myApp.directives').directive('myValidate', [function() {
return {
link: function(scope, elem, attrs) {
elem.on('keyup', function() {
// do some custom validation
// if valid, set elem to $valid
// else set $error
// something like elem.set('$valid');
}
}
};
}]);
How can I achieve the combination of HTML5, Angular and custom form validation while using Angulars testform.testfield.$invalid, testform.testfield.$valid, testform.testfield.$error etc. logic?
By default Angular provides some directives for input validation (like required, pattern, min, max etc.). If these directives doesn't suffice, custom validation is available. To be able to do that, the directive requires the model used for validation, then it can set the validity of the element like below:
.directive('myValidate', [ function() {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ctrl) {
ctrl.$setValidity('myValidate', false);
}
};
}]);
A Plunker with such a directive which will become invalid when you enter '0' is HERE.
You can read more about custom validation on Angular forms documentation.
You can do custom validation and combine it with Angular's built in validation, you just need to require ngModel, pass ctrl as the 4th value to your link function, and use ctrl.$setValidity to indicate if your custom validation passes or fails.
The example below will be invalid if the length < 3 OR if the value is the string 'invalid':
var app = angular.module('app', []);
app.controller('controller', ['$scope',
function(scope) {
scope.test = {
name: ''
};
}
]);
app.directive('myValidate', [
function() {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ctrl) {
ctrl.$parsers.unshift(function(value) {
var valid = true;
if (value === 'invalid') {
valid = false;
}
ctrl.$setValidity(attrs.name, valid);
return valid ? value : undefined;
});
}
};
}
]);
Markup:
<body ng-app="app">
<div ng-controller="controller">
<form name="testform">
<input name="testform" type="text" ng-model="test.name" ng-minlength="3" my-validate /><br/>
</form>
<span style="color:green" ng-show="testform.testform.$valid">** valid **</span>
<span style="color:red" ng-show="testform.testform.$invalid">** invalid **</span>
</div>
<script src="script.js"></script>
</body>
Here is a plunker with this code: http://plnkr.co/edit/6XIKYvGwMSh1zpLRbbEm
The Angular documentation talks about this, though it wasn't totally obvious: https://docs.angularjs.org/api/ng/type/ngModel.NgModelController
This blog helped me get a hold on this: http://www.benlesh.com/2012/12/angular-js-custom-validation-via.html

Resources