This is what I wrote but angular keeps complaining about the $asyncValidators telling me is {}. I do not want to work with $http nor with $q. This is the faulty code I wrote:
.directive('checkEmail', ['toolBox', function(toolBox){
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModel){
ngModel.$asyncValidators.emailExists = function(userEmail){
var promise = toolBox.checkEmailExist(userEmail);
return promise.get(function success(res){
return true;
}, function error(res){
return false;
});
};
}
};
}])
Has any of you worked validation with $resource? Where I get it wrong? toolBox.checkEmailExist(userEmail) comes from a service that looks like this
angular.module('toolBoxService', ['ngResource'])
.factory('toolBox', ['$resource','$log', function($resource, $log){
var dataObj = {};
dataObj.checkEmailExist = function(email){
return $resource('api/users/email', {email: email});
};
return dataObj;
}]);
and the form element looks like
<!-- Email field -->
<div class="input-group">
<span class="input-group-addon" id="userEmail">*</span>
<label class="control-label" for="userEmailField"></label>
<input class="form-control"
name="userEmail"
id="userEmailField"
required=""
placeholder="Email"
type="email"
ng-model="data.email"
ng-model-options="{ debounce: { default : 300, blur: 0 }}"
check-email>
</input>
</div>
<!-- Validation of the email -->
<div class="help-block form-error-messages"
ng-messages="registerForm.userEmail.$error"
ng-show="registerForm.userEmail.$touched"
role="alert"
ng-messages-multiple>
<div ng-messages-include="FACETS/errors/errorMessages.html"></div>
</div>
The only solution I've came up with is this
// checking for email in the database
.directive('checkEmail', ['toolBox', function(toolBox) {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
ngModel.$validators.checkEmail = function(modelValue, viewValue){
var currentVal = modelValue || viewValue;
toolBox.checkEmailExist(currentVal)
.get(function success(resp){
if(resp.email == currentVal) {
ngModel.$setValidity('checkEmail', false);
} else {
ngModel.$setValidity('checkEmail', true);
}
});
};
} // end link
}; // end return
}])
$resource is not working with the $asyncValidators unfortunately. Took me 2 days to realise this. I hope there was an answer, a more expert voice on the matter.
It might be that you have to return the $promise attached to the resource instance object, rather than the object itself.
If your api returns an OK 200 response when the username does exist and an error status (e.g. 404 Not Found) when it does not, then the following approach works:
.directive('usernameExists', ['$resource', function ($resource) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, elem, attrs, ngModel) {
ngModel.$asyncValidators.usernameExists = function (userName) {
return $resource('/api/Account/:userName')
.get({ userName: userName }).$promise;
};
}
}
}])
I can solve it, it was matter of playing a bit more with promises
.directive('validateNbr',['$q','PreavisoService', function($q, PreavisoService) {
return {
require: 'ngModel',
restrict: '',
link: function(scope, elm, attrs, ngModel) {
ngModel.$asyncValidators.validateNbr = function(modelValue){
var def = $q.defer();
PreavisoService.checkUnitDigit({nbr:ngModel.$viewValue}).$promise.then(
function(result){
if(result.isValid === false){
def.reject();
}else{
def.resolve();
}
});
return def.promise;
};
}
};
}]);
hope it helps
Related
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;
});
}
};
}
]);
What would be the best way to show data returned by $asyncValidator when form field is valid?
I can show an error besides the input field with ngMessage but would also like to show the resulting response from Restangular when validation passes.
'use strict';
angular.module('app')
.directive('productionOrder', function(Restangular) {
return {
restrict: "A",
require: "ngModel",
link: function(scope, element, attributes, ngModel) {
ngModel.$asyncValidators.exists = function(modelValue) {
return Restangular.one('production_orders', modelValue).get();
}
}
};
});
Html
<form name="workOrderForm">
<input type="text" name="productionOrderNo" ng-model="work_order.work_order.production_order_no" required production-order ng-model-options="{ debounce: 1000 }">
<div ng-messages="workOrderForm.productionOrderNo.$error">
<div ng-message="required">Production order no is mandatory</div>
<div ng-message="exists">Production order not found</div>
</div>
</form>
I'm sure this breaks at least a couple of best practises but this is how I got it working for now.
angular.module('oeeMaster')
.directive('productionOrder', function($q, Restangular) {
return {
restrict: "A",
require: "ngModel",
link: function($scope, element, attributes, ngModel) {
ngModel.$asyncValidators.exists = function(modelValue) {
var deferred = $q.defer();
Restangular.one('production_orders', modelValue).get().then(
function(response) {
ngModel.part_no = response.part_no;
deferred.resolve(response);
}, function() {
ngModel.part_no = null;
deferred.reject();
}
);
return deferred.promise;
}
}
};
});
HTML
<div ng-if="workOrderForm.productionOrderNo.part_no">Part number: {{ workOrderForm.productionOrderNo.part_no }}</div>
I hope u can help me.
I have a directive:
.directive('checkField', ['$http', function($http) {
return {
require: 'ngModel',
restrict: 'A',
link: function (scope, ele, attrs, c) {
scope.$watch(function() {
if (attrs.ngModel === 'data.gender_value' && ele.val() !== '') {
//valid
} else {
//error
}
if (attrs.ngModel === 'data.cardholder_value' && ele.val() !== '') {
//valid
} else {
//error
}
});
},
template: ''
};
}])
And i have multiple inputs in my html:
<input ng-model="data.cardholder_value" type="text" size="50" data-check-field />
<input ng-model="data.gender_value" type="text" ng-required="true" data-check-field />
The problem is that watch trigger only "see" the first input, no more.
I'm trying to use de same directive to multiple inputs, but doesn't work. If i do an alert, to check the attribute name of field, always display "data.cardholder_value", never other name field.
Thank u in advance.
Edit 1:
This is my html calling (ng-include):
<form method="post" id="formQuestion" name="formQuestion" ng-submit="sendForm()" novalidate ng-controller="questionForm">
{{data | json}}
<div class="slide-animate" ng-include="'/templates/default/partials/_fields/1_card_type.html'"></div>
<div class="slide-animate" ng-include="'/templates/default/partials/_fields/2_gender.html'"></div>
My app controller:
angular.module('app.controllers')
.directive('checkField', ['$http', function($http) {
return {
require: 'ngModel',
restrict: 'A',
link: function (scope, ele, attrs, ctrl) {
scope.$watch(attrs.ngModel, function(val) {
console.log(attrs.ngModel, attrs.name, val)
});
},
template: ''
};
}])
.controller('questionForm', ['$scope', '$http', 'fieldApiService', function ($scope, $http, fieldApiService) {
...
All you just need it to watch the value of ng-model directive attribute, right now you provided the watcher function as your validation function which mean when it sees function as first argument for the watch it will just only look for the return value from that function to determine if watch listener needs to run or not during every digest cycle.
scope.$watch(attrs.ngModel, function(val) {
if (!val) {
//valid
} else {
//error
}
});
Also remember if you want to catch the user entered values you can always use the existing $viewChangeListener property on ngmodel, it will avoid reuse of existing internal watcher and no need to explicitly create one.
c.$viewChangeListeners.push(function(){
console.log('viewChange', ctrl.$viewValue)
});
Demo
angular.module('app', []).directive('checkField', ['$http',
function($http) {
return {
require: 'ngModel',
restrict: 'A',
link: function(scope, ele, attrs, ctrl) {
scope.$watch(attrs.ngModel, function(val) {
console.log(attrs.ngModel, attrs.name, val)
});
ctrl.$viewChangeListeners.push(function(){
console.log('viewChange', ctrl.$viewValue)
});
},
};
}
])
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<input ng-model="data.cardholder_value" name="cardholder" type="text" size="50" data-check-field />
<input ng-model="data.gender_value" name="gender" type="text" ng-required="true" data-check-field />
</div>
In a custom validation directive I ensure that the entered value does not conflict with others in the database. If it does, I would like to tell the user not only that there's a conflict, but also the name of the item it is conflicting with.
Storing these details in the scope would probably work, but doesn't seem right to me at all. Is there a better way?
Directive:
angular.module('myApp')
.directive('conflictcheck', function (myServer) {
return {
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
ctrl.$parsers.unshift(function (viewValue) {
var conflict = myServer.getConflict(viewValue);
if (!conflict) {
ctrl.$setValidity('conflictcheck', true);
return viewValue;
} else {
ctrl.$setValidity('conflictcheck', false);
// pass additional info regarding the conflict here
return undefined;
}
});
}
};
});
View:
<form name="myform" action="#">
<input ng-model="foo" conflictcheck />
<div ng-if="myform.foo.$error.conflictcheck">
Error: Foo conflicts with XXXXXXXX!
</div>
</form>
Validation errors are handled by FormController so it's reasonable to keep all validation-related data inside the form object itself. This could be implemented by attaching arbitrary data to myform instance:
app.factory('myServer', function(){
return {
getConflict: function(viewValue){
var comparedAgainst = 'existing#email.com';
if(viewValue === comparedAgainst){
return comparedAgainst;
}
}
}
});
app
.directive('conflictcheck', function (myServer) {
return {
require: ['ngModel', '^form'],
link: function (scope, elm, attrs, ctrls) {
var ngModelCtrl = ctrls[0];
var ngFormCtrl = ctrls[1];
ngModelCtrl.$parsers.unshift(function (viewValue) {
// getConflict returns conflicting value
var conflict = myServer.getConflict(viewValue);
if (!conflict) {
ngModelCtrl.$setValidity('conflictcheck', true);
return viewValue;
} else {
ngModelCtrl.$setValidity('conflictcheck', false);
// We attach validation-specific data to arbitrary data property
ngFormCtrl.foo.data = {
conflictcheck: conflict
};
return undefined;
}
});
}
};
});
<form name="myform" novalidate>
Email:
<input ng-model="foo" name="foo" conflictcheck />
<div ng-show="myform.foo.$error.conflictcheck">
Error: Foo conflicts with {{myform.foo.data.conflictcheck}}!
</div>
</form>
Plunker
Not sure how to phrase the question so please edit if you can come up with something better. I have the following directive:
app.directive('foo', function() {
return {
restrict: 'A',
require: "?ngModel",
link: function (scope, element, attrs, controller) {
scope.$watch(attrs.ngModel, function () {
console.log("Changed to " + scope[attrs.ngModel]);
});
}
};
});
When I have this it works great and logs properly
<input type="text" ng-model="bar" />
app.controller('fooController', function($scope) {
$scope.bar = 'ice cream';
});
It doesn't work when I try it this way around. It keeps logging 'Changed to undefined'
<input type="text" ng-model="model.bar" />
app.controller('fooController', function($scope) {
$scope.model = { bar: 'ice cream' };
});
How do I make it work for both scenarios. It seems the right thing to do seeing as angular lets you use both.
I looked at ngModel directive and found a function called ngModelGet. Uses $parse.
app.directive('foo', function($parse) {
return {
restrict: 'A',
require: "?ngModel",
link: function (scope, element, attrs, controller) {
var ngModelGet = $parse(attrs.ngModel);
scope.$watch(attrs.ngModel, function () {
console.log("Changed to " + ngModelGet(scope));
});
}
};
});
your can use
var ngModelCtrl = controller;
ngModelCtrl.$viewValue
replace
scope[attrs.ngModel]
here is ngModelCtrl sdk