Angular async validation with multiple form fields - angularjs

I have a async validation directive which works fine when, but it depends on two fields to define if a person exists (numId and type), here's the code:
app.directive('personaUnica', function($http, $q){
return{
require:'ngModel',
scope: {
tipo: "=personaUnica"
},
link: function(scope, element, attrs, ctrl){
ctrl.$asyncValidators.personaUnica = function(modelValue, viewValue){
if (ctrl.$isEmpty(modelValue)) {
// valido si es vacio
return $q.when();
}
var defer = $q.defer();
$http.get('/someRESTEndpoint/', {
params:{ identificacion: modelValue, tipo: scope.tipo }
}).then(function(respuesta){
//Person found, not valid
if( respuesta.data.elementoExiste ){
defer.reject('Persona existe.');
}
}, function(respuesta){
//Person not found, resolve promise
if(!respuesta.data.elementoExiste){
defer.resolve();
}
});
return defer.promise;
}
}
}
});
But I dont know how to make the same validation when the other dependant field has changed.
I've read something about require ^form in the directive but in kinda lost.
I've tried to add this block of code
scope.$watch('tipo', function(){
ctrl.$validate();
});
But then I get an infinite $digest loop.
Thanks in advance.

You can do something like this:
$scope.$watch('tipo', function(newValue, oldValue, scope) {
if (newValue != oldValue) {
ctrl.$validate();
}
});
This way, $scope.watch will be called everytime you have a new value on your $scope.tipo.

Turns out that I was using watch in the wrong place inside the ctrl.$asyncValidators.numId, it has to be outside. Now It works as expected.
app.directive('numId', function($http, $q){
return {
restrict : 'A',
scope: {
tipo: "=numId"
},
require: 'ngModel',
link: function(scope, element, attrs, ctrl){
ctrl.$asyncValidators.numId = function(modelValue, viewValue){
if (ctrl.$isEmpty(modelValue) || ctrl.$isEmpty(scope.tipo)) {
// consider empty model valid
console.log('Not going yet..');
return $q.when();
}
var defer = $q.defer();
$http.get("/some-server/endpoint",{
params:{ identificacion: modelValue, tipo: scope.tipo }
}).then(function(res){
if( res.data.personaExiste){
console.log('exists, not valid')
defer.reject('exists, not valid');
}else if( !res.data.personaExiste ){
console.log('NOT exists, valid!')
defer.resolve();
}
}, function(){
defer.reject('Error...');
});
return defer.promise;
}
// Search when tipo is changed
scope.$watch('tipo', function(){
console.log('Tipo:' + scope.tipo)
ctrl.$validate();
});
}
}
});
And the html:
<div class="form-group">
<label>Numero identificacion</label>
<input type="text"
name="numId"
required
ng-model="busqueda.numId"
num-id="busqueda.tipoUsuario">
<pre class="well"> {{ frmBuscar.numId.$error }} </pre>
</div>

Related

Cannot set ng-model value to null from directive

In the example below I am trying to set controller value to null, when user closes popup window with error. But angularjs somehow takes old value, despite I am updating $viewValue and $modelValue. If you type in input any value, for example 1, and press Tab, then after 1 second you will see 1.00, however I am trying to set it to null (and view value should be empty string).
(function () {
var module = angular.module('TestApp', []);
module.directive('decimalTextbox', ['$timeout', function ($timeout) {
return {
restrict: 'A',
replace: false,
require: '?ngModel',
scope: {
onValidate: '='
},
link: function (scope, element, attrs, ctrl) {
ctrl.$formatters.unshift(function (value) {
return value != null ? parseFloat(value).toFixed(2) : '';
});
ctrl.$parsers.unshift(function (value) {
return value == '' ? null : parseFloat(value);
});
$(element).on('blur', function () {
$timeout(function () {
var result = { isValid: true, value: ctrl.$modelValue };
scope.onValidate(result);
if (!result.isValid) {
// show popup with close button here
// to simplify example I use setTimeout
setTimeout(function () {
$(element).val('');
ctrl.$viewValue = '';
ctrl.$modelValue = null;
scope.$apply();
console.log(ctrl.$modelValue);
}, 1000);
}
});
});
}
};
}]);
module.controller('TestController', ['$scope', function (scope) {
scope.someValue = 3;
scope.validate = function (result) {
result.isValid = false;
};
}]);
})();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.6/angular.min.js"></script>
<div ng-app="TestApp" ng-controller="TestController">
<input
type="text"
ng-model="someValue"
decimal-textbox=""
on-validate="validate">
<h3>{{ someValue }}</h3>
</div>
To manipulate $viewValue, use $setViewValue function, it will take care update $modelValue and call intermediate $$parsers, $$formatters and $$validators pipeline behind the scenes.
//ctrl.$viewValue = ''; //instead of this
ctrl.$setViewValue(''); //use this.
//ctrl.$modelValue = null; //no need to set $modelValue

how to execute the statement after promise is executed?

I have used the following directory in my template
i want to change the model value of drop-down to id for it i have used as bellow
<md-select flex class="md-select-form" ng-model="education.degree" placeholder="Degree" save-id id="education.degree_id" ">
<md-option ng-value="degree" ng-repeat="degree in ['High School', 'Associates Degree', 'Bachelor Degree', 'Masters Degree', 'M.B.A', 'M.D', 'Ph.D', 'other']">{{degree}}</md-option>
</md-select>
.directory code
.directive('saveId', function(Profile){
return {
require: 'ngModel',
scope: {
id: '=',
requiredParam:'#'
},
link: function(scope, element, attrs, ngModel) {
console.log("initial loading");
// view --> model (change to string)
ngModel.$parsers.push(function(viewValue){
var keepGoing = true;
Profile.getDegreeList().then(function(data) {
angular.forEach(data, function(ob){
if(keepGoing) {
if(ob.degree == viewValue){
scope.id = ob.degree_id;
keepGoing = false;
}
}
});
console.log("within promise"+scope.id); //it executes second
});
console.log(scope.id); //it executes first
return scope.id;
});
return scope.id;
}
};
})
Once if i try to return the value of ng-model in finally block it also not working
.directive('saveId', function(Profile){
return {
require: 'ngModel',
scope: {
id: '=',
requiredParam:'#'
},
link: function(scope, element, attrs, ngModel) {
console.log("initial loading");
// view --> model (change to string)
ngModel.$parsers.push(function(viewValue){
// var id = -1;
var keepGoing = true;
Profile.getDegreeList().then(function(data) {
angular.forEach(data, function(ob){
if(keepGoing) {
if(ob.degree == viewValue){
scope.id = ob.degree_id;
keepGoing = false;
}
}
});
}).finally(function(res){
console.log(scope.id);
return scope.id;
});
});
return scope.id;
}
};
})
i have used Profile.getDegreeList() service to assign the id to relevant drop-down element by using ngModel.$parsers.push(). The problem is in case of service usage before promise going to complete it returns the previous assigned id .
i want to prevent it and need to return the promise id.
how to solve this issue please help?
You can use finally method which will be executed after executing promise.
Profile.getDegreeList()
.success(function(data)
{
angular.forEach(data, function(ob)
{
if (keepGoing)
if (ob.degree == viewValue) {
scope.id = ob.degree_id;
keepGoing = false;
}
});
console.log("within promise"+scope.id);
})
.finally(function(res)
{
// your code
}
);
Here you go first make a call and then use parsers.
Profile.getDegreeList().then(function(data) {
angular.forEach(data, function(ob) {
if (keepGoing) {
if (ob.degree == viewValue) {
scope.id = ob.degree_id;
keepGoing = false;
}
}
});
ngModel.$parsers.push(function(viewValue) {
var keepGoing = true;
return scope.id ;
});
});
See other links too for more guidance.

directive angularjs to check mail used or not

i want to check if email is already used or not (communication with back-end) but the error message is not shown in the screen.
the verficiation function service :
demoApp.factory('verifyEmail', function($http) {
return function(mail) {
var test=mail;
var urll="http://localhost:8080/app/personne/verifmail?msg=";
var aplrest=urll+test;
var ch3=aplrest;
return $http.post(ch3)
};});
the directive (with the help of #jsonmurphy)
demoApp.directive('existTo', ["$q","verifyEmail",function ($q, verifyEmail) {
return {
require: "ngModel",
scope: {
otherModelValue: "=existTo"
},
link: function(scope, element, attributes, ngModel) {
// Note the change to $asyncValidators here <-------------
ngModel.$asyncValidators.existTo = function(modelValue) {
var deferred = $q.defer();
verifyEmail(modelValue).then(function(respons) {
// the respons can be {exist="true"} or{exist="false"}
var rep=respons.exist;
deferred.resolve(rep);
});
return deferred.promise;
};
scope.$watch("otherModelValue", function() {
ngModel.$validate();
});
}
}; }]);
the html file:
<label>Email</label>
<input type="email" name="email" class="form-group"
ng-model="registration.user.email"
ng-pattern="/^[_a-z0-9]+(\.[_a-z0-9]+)*#[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,8})$/"
required exist-To="registration.user.email"/>
<span style="color:red" ng-show="registrationForm.email.$dirty && registrationForm.email.$invalid">
<span ng-show="registrationForm.email.$error.required">champs email obligatoire.</span>
<span ng-show="registrationForm.email.$error.pattern">Invalid email address.</span>
<span ng-show="registrationForm.email.$error.existTo">mail already exist.</span>
</span>
this the solution
demoApp.directive('existTo', ["$q","verifyEmail",function ($q, verifyEmail) {
return {
require: "ngModel",
scope: {
otherModelValue: "=existTo"
},
link: function(scope, element, attributes, ngModel) {
// Note the change to $asyncValidators here <-------------
ngModel.$asyncValidators.existTo = function(modelValue) {
var deferred = $q.defer();
// window.alert("l email a tester "+modelValue);
verifyEmail(modelValue).success(function(respons) {
// the respons can be {exist="true"} or{exist="false"}
// window.alert("la reponse " +respons);
var rep=respons.exist;
// window.alert("la reponse " +rep);
if (rep =="false") {
// The username is available
deferred.resolve();
} else {
deferred.reject();
}
});
return deferred.promise;
};
scope.$watch("otherModelValue", function() {
ngModel.$validate();
});
}
}; }]);

verify email is already used or not angularjs

I start with angularJS and I want to check if the email entered in the form already exists or not . for that, I will use RESTto communicate withe back_end.My problem is : how to check email before sending the form with a directive and display an error message if the email is already used.
<form name="registrationForm">
<div>
<label>Email</label>
<input type="email" name="email" class="form-group"
ng-model="registration.user.email"
ng-pattern="/^[_a-z0-9]+(\.[_a-z0-9]+)*#[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,8})$/"
required compare-to="registration.user.email"
/>
the model
demoApp.directive('existTo', [function () {
return {
require: "ngModel",
scope: {
otherModelValue: "=existTo"
},
link: function(scope, element, attributes, ngModel) {
ngModel.$validators.existTo = function(modelValue) {
return modelValue == scope.otherModelValue;
};
scope.$watch("otherModelValue", function() {
ngModel.$validate();
});
}
}; }]);
back_end checkmail: the controller
demoApp.controller('signupCtrl',function($scope,$http) {
$scope.verficationEmail = function(mail) {
$scope.test = mail;
var urll="";
var test="";
$scope.urll="http://localhost:8080/app/personne/verifmail?msg=";
$scope.aplrest=$scope.urll+$scope.test;
var ch3=$scope.aplrest;
$http.post(ch3).
success(function(respons) {
$scope.data = respons;
$scope.valid = respons.valid;
if ( $scope.valid == "false") {
$scope.msgalert="mail used";
}
})
};});
thank you in advance for your assistance
You will most likely want to put the verficiation function in a service and inject it wherever it's needed:
demoApp.factory('verifyEmail', function() {
return function(mail) {
var test=mail;
var urll="http://localhost:8080/app/personne/verifmail?msg=";
var aplrest=urll+test;
var ch3=aplrest;
return $http.post(ch3)
};
});
Then...
In your directive:
demoApp.directive('existTo', ["$q","verifyEmail",function ($q, verifyEmail) {
return {
require: "ngModel",
scope: {
// This is probably not needed as ngModel's
// validators will pass the current value anyway
otherModelValue: "=existTo"
},
link: function(scope, element, attributes, ngModel) {
// Note the change to $asyncValidators here <-------------
ngModel.$asyncValidators.existTo = function(modelValue) {
var deferred = $q.defer()
verifyEmail(modelValue).then(function(respons){
deferred.resolve(respons.valid);
});
return deferred.promise
};
scope.$watch("otherModelValue", function() {
ngModel.$validate();
});
}
}; }]);
In your controller:
demoApp.controller('signupCtrl',function($scope,$http,verifyEmail) {
$scope.verficationEmail = function (mail) {
verifyEmail(mail).success(function(respons) {
$scope.data = respons;
$scope.valid = respons.valid;
if ( $scope.valid == "false") {
$scope.msgalert="mail used";
}
});
}
});
Also, in your html you dont seem to be actually using the directive exist-to, I'm guessing that's what compare-to should have been. But, in any case, it might look like this:
<input type="email" name="email" class="form-group"
ng-model="registration.user.email"
ng-pattern="/^[_a-z0-9]+(\.[_a-z0-9]+)*#[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,8})$/"
required exist-to
/>
Note that there's no need to pass the model value to the directive
Hope that helps.

$asyncValidators with params for two modes ('new' and 'edit' form)

I'm using $asyncValidators to check if email is available or not.
I have only one controller and template for two modes : 'new' and 'edit'.
i created a directive for this check. But on 'edit' mode, i don't want to check current email. So, in my controller, i've created $scope.initialEmail to compare it with entered email. But i don't know how to use it in directive (for edit mode).
Template :
<input id="email" name="email" type="email" class="form-control"
placeholder="{{'placeholders_email'|i18n}}"
ng-model="user.email" ng-required="true" email-available/>
<ng-messages for="myForm.email.$error" ng-if="myForm.email.$dirty">
<ng-message class="red" when="emailAvailable">{{'email_exists_in_db'|i18n}}</ng-message>
...
Controller :
//formMode is injected in controller ('new' or 'edit')
$scope.formMode = angular.copy(formMode);
$scope.user = {email: '...', ...};
switch(formMode){
case 'edit':
$scope.initialEmail = angular.copy($scope.user.email);
break;
}
Factory :
//AuthHttp is an auth service using $http
angular.module('myapp').factory('EmailAvailableValidator', ['$q', 'AuthHttp', function($q, AuthHttp) {
return function(email) {
var deferred = $q.defer();
AuthHttp.get('/rest/users/emailAvailable/'+email).then(function() {
deferred.resolve();
}, function() {
deferred.reject();
});
return deferred.promise;
}}]);
Directive :
angular.module('myapp').directive('emailAvailable', ['EmailAvailableValidator', function(EmailAvailableValidator) {
return {
restrict: "A",
require : 'ngModel',
link : function(scope, element, attrs, ngModel) {
switch(scope.formMode){
case 'new':
ngModel.$asyncValidators.emailAvailable = EmailAvailableValidator;
break;
case 'edit':
ngModel.$asyncValidators.emailAvailable = function(email){ // ?
if(!_.isEqual(scope.initialEmail, ngModel.$modelValue)){ // ?
return EmailAvailableValidator; // ??
}
};
break;
}
}
}
}]);
The solution is to add $q.when for the edit mode like this :
Factory :
angular.module('myapp').factory('UsersService', ['AuthHttp', '$q', function (AuthHttp, $q) {
return {
emailAvailable: function(email){
var deferred = $q.defer();
AuthHttp.get('/rest/users/emailAvailable/'+email).then(function() {
deferred.resolve();
}, function() {
deferred.reject();
});
return deferred.promise;
}
}
}]);
Directive :
angular.module('myapp').directive('emailAvailable', ['UsersService', '$q', function(UsersService, $q) {
return {
restrict: "A",
require : 'ngModel',
link : function(scope, element, attrs, ngModel) {
switch(scope.formMode){
case 'new':
ngModel.$asyncValidators.emailAvailable = function(modelValue, viewValue) {
return UsersService.emailAvailable(modelValue || viewValue);
};
break;
case 'edit':
ngModel.$asyncValidators.emailAvailable = function(modelValue, viewValue){
var newValue = modelValue || viewValue;
var promise;
if(!_.isEqual(scope.initialEmail, newValue)){
promise = UsersService.emailAvailable(newValue);
} else {
promise = $q.when(!_.isEqual(scope.initialEmail, newValue));
}
return promise;
};
break;
}
}
}
}]);

Resources