I'd like to enforce unique usernames in my Firebase app and immediately let the user know if the inserted username is already taken or not.
I looked into AngularJS's ngModel since it has a built-in asyncValidator in its controller (example in Custom Validation), but I'm still stuck in get it work.
html
<form name="settings" novalidate>
<div>
Username:
<input type="text" ng-model="user.username" name="username" app-username><br>
<span ng-show="settings.username.$pending.appUsername">Checking if this username is available...</span>
<span ng-show="settings.username.$error.appUsername">This username is already taken!</span>
</div>
</form>
directive
app.directive('appUsername', function() {
return {
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
var ref = new Firebase("https://<MY-APP>.firebaseio.com");
ctrl.$asyncValidators.appUsername = function (modelValue, viewValue) {
if (ctrl.$isEmpty(modelValue)) {
// consider empty model valid
return true;
}
var q = ref.child('users').orderByChild('username').equalTo(modelValue);
q.once('value', function (snapshot) {
if (snapshot.val() === null) {
// username does not yet exist, go ahead and add new user
return true;
} else {
// username already exists, ask user for a different name
return false;
}
});
};
};
};
});
Is it possible to use Firebase in an asyncValidator?
Thanks in advance.
Yes, it's possible. However, the issue you're running into isn't Firebase related. Per the Angular docs you linked:
Functions added to the object must return a promise that must be resolved when valid or rejected when invalid.
So you simply need to use a deferred as done in the Angular docs, or use $q(function(resolve, reject) {...}). I've used the latter below.
app.directive('appUsername', function($q) {
return {
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
var ref = new Firebase("https://<MY-APP>.firebaseio.com");
ctrl.$asyncValidators.appUsername = function (modelValue, viewValue) {
return $q(function(resolve, reject) {
if (ctrl.$isEmpty(modelValue)) {
// consider empty model valid
resolve();
}
var usernameRef = ref.child('users').orderByChild('username').equalTo(modelValue);
usernameRef.once('value', function (snapshot) {
if (snapshot.val() === null) {
// username does not yet exist, go ahead and add new user
resolve();
} else {
// username already exists, ask user for a different name
reject();
}
});
});
};
};
};
});
Related
I use $asyncValidators to validate a field value. The field value is fetched from server with $resource. On page load, the value is empty and the validator sends a request to the server. When $resource has fetched the resource, the field value is populated and the validator sends another request to the server.
I would like to skip these two initial requests since they are useless. How do I do that?
My input field:
<input name="name" class="form-control" ng-model="user.name" validate-name>
My directive:
angular.module('myapp').directive('validateName', function($http, $q) {
return {
require: 'ngModel',
link: function(scope, element, attrs, ctrl) {
ctrl.$asyncValidators.name = function(modelValue, viewValue) {
return $http.post('/validate-username', {username: viewValue}).then(function(response) {
if (!response.data.validUsername) {
return $q.reject(response.data.errorMessage);
}
return true;
});
};
}
};
});
A reasonable condition is for the model to be touched; if it isn't, just return a resolved promise, without accessing the server. I.e.:
ctrl.$asyncValidators.name = function(modelValue, viewValue) {
if( ctrl.$touched ) {
return $http.post('/validate-username', {username: viewValue})....
}
else {
return $q.resolve(true);
}
};
In the documentation it shows this:
ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) {
var value = modelValue || viewValue;
// Lookup user by username
return $http.get('/api/users/' + value).
then(function resolved() {
//username exists, this means validation fails
return $q.reject('exists');
}, function rejected() {
//username does not exist, therefore this validation passes
return true;
});
};
But how do I connect this with an input field? It doesn't give an example of this.
You'd apply your validator inside of a directive link function. The directive must require ngModel, and then you'd apply that directive as an attribute to the input tag. In your case, that might look a bit like this:
angular.directive('isUniqueName', function () {
return {
require: 'ngModel',
link: function ($scope, $elem, $attrs, ngModel) {
ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) {
var value = modelValue || viewValue;
// Lookup user by username
return $http.get('/api/users/' + value).
then(function resolved() {
//username exists, this means validation fails
return $q.reject('exists');
}, function rejected() {
//username does not exist, therefore this validation passes
return true;
})
}
}
}
})
And then in your HTML code:
<input type='text' name='username' ng-model='username' is-unique-name />
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.
I have a range input (between [lowerValue] and [upperValue]) on a form and I want to make a reusable directive called 'validateGreaterThan' that can be attached to any form and use the ngModel $validators functionality so I can attach multiple ones onto an input.
You can check a simple demo on jsbin here:
http://jsbin.com/vidaqusaco/1/
I've set up a directive called nonNegativeInteger and that works correctly, however, the validateGreaterThan directive I have isn't working. How can I get it to reference the lowerValue?
I appreciate any help with this.
Here is the basic idea:-
Define 2 directives and let each directive refer to the other field buy passing its name. When the validator runs on the current field you can retrieve the model value of another field and now you have values from both fields and you can ensure the relation between the 2 fields.
As per my code below I have 2 fields minimumAmount and maximumAmount where the minimumAmount cannot be greater than the maximum amount and vice-versa.
<input name="minimumAmount" type="number" class="form-control"
ng-model="entity.minimumAmount"
less-than-other-field="maximumAmount" required/>
<input name="maximumAmount" type="number"
ng-model="entity.maximumAmount"
greater-than-other-field="minimumAmount"
class="form-control"/>
Here we have 2 directives lessThanOtherField and greaterThanOtherField and they both refer to other field as we pass the other field name. greater-than-other-field="minimumAmount" we are passing the other field.
.directive('lessThanOtherField', ['$timeout',function($timeout){
return {
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
var xFieldValidatorName = 'lessThanOtherField';
var form = elm.parent().controller('form');
var otherFieldName = attrs[xFieldValidatorName];
var formFieldWatcher = scope.$watch(function(){
return form[otherFieldName];
}, function(){
formFieldWatcher();//destroy watcher
var otherFormField = form[otherFieldName];
var validatorFn = function (modelValue, viewValue) {
var otherFieldValue = otherFormField.hasOwnProperty('$viewValue') ? otherFormField.$viewValue : undefined;
if (angular.isUndefined(otherFieldValue)||otherFieldValue==="") {
return true;
}
if (+viewValue < +otherFieldValue) {
if (!otherFormField.$valid) {//trigger validity of other field
$timeout(function(){
otherFormField.$validate();
},100);//avoid infinite loop
}
return true;
} else {
// it is invalid, return undefined (no model update)
//ctrl.$setValidity('lessThanOtherField', false);
return false;
}
};
ctrl.$validators[xFieldValidatorName] = validatorFn;
});
}
};
}])
.directive('greaterThanOtherField', ['$timeout',function($timeout){
return {
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
var xFieldValidatorName = 'greaterThanOtherField';
var form = elm.parent().controller('form');
var otherFieldName = attrs[xFieldValidatorName];
var formFieldWatcher = scope.$watch(function(){
return form[otherFieldName];
}, function(){
formFieldWatcher();//destroy watcher
var otherFormField = form[otherFieldName];
var validatorFn = function (modelValue, viewValue) {
var otherFieldValue = otherFormField.hasOwnProperty('$viewValue') ? otherFormField.$viewValue : undefined;
if (angular.isUndefined(otherFieldValue)||otherFieldValue==="") {
return true;
}
if (+viewValue > +otherFieldValue) {
if (!otherFormField.$valid) {//trigger validity of other field
$timeout(function(){
otherFormField.$validate();
},100);//avoid infinite loop
}
return true;
} else {
// it is invalid, return undefined (no model update)
//ctrl.$setValidity('lessThanOtherField', false);
return false;
}
};
ctrl.$validators[xFieldValidatorName] = validatorFn;
});
}
};
}])
html
<input type="email" data-input-feedback="" data-ng-model="user.email" data-unique-email="" required="required" placeholder="Email" class="form-control" id="email" name="email">
js
.directive('uniqueEmail',function (User) {
return {
require:'ngModel',
restrict:'A',
link:function (scope, element, attrs, ngModelCtrl) {
ngModelCtrl.$parsers.push(function (viewValue) {
/*
Is there a way to check if it's a valid email ?
both ngModelCtrl.$valid and ngModelCtrl.$error.email doesn't work
*/
User.isUniqueEmail(viewValue).then(function(data){
ngModelCtrl.$setValidity('uniqueEmail', !data.email);
});
return viewValue;
});
}
};
});
so is there a way to check if it's a valid email
before sending the value to the server ?
UPDATE
ngModelCtrl.$parsers.push
using push() here to run it as the last parser, after we are sure that other validators were run
so only if required and email validation are passed
do the call to check for unique email
END UP
.directive('uniqueEmail',function (User) {
return {
require:'ngModel',
restrict:'A',
controller:function ($scope) {
$scope.isValidEmail = function(){
return $scope.form.email.$error.email;
}
},
link:function (scope, element, attrs, ngModelCtrl) {
var original;
// If the model changes, store this since we assume it is the current value of the user's email
// and we don't want to check the server if the user re-enters their original email
ngModelCtrl.$formatters.unshift(function(modelValue) {
original = modelValue;
return modelValue;
});
// using push() here to run it as the last parser, after we are sure that other validators were run
ngModelCtrl.$parsers.push(function (viewValue) {
if (viewValue && viewValue !== original ) {
if(scope.isValidEmail(viewValue)){
User.isUniqueEmail(viewValue).then(function(data){
ngModelCtrl.$setValidity('uniqueEmail', !data.email);
});
}
return viewValue;
}
});
}
};
});
With compile and priority
.directive('uniqueEmail',function (User) {
return {
require:'ngModel',
restrict:'A',
priority:0,
compile: function compile(tElement, tAttrs, transclude) {
return function (scope, element, attrs, ngModelCtrl) {
var original;
// If the model changes, store this since we assume it is the current value of the user's email
// and we don't want to check the server if the user re-enters their original email
ngModelCtrl.$formatters.unshift(function(modelValue) {
original = modelValue;
ngModelCtrl.$setViewValue(original);
return modelValue;
});
// using push() here to run it as the last parser, after we are sure that other validators were run
ngModelCtrl.$parsers.push(function (viewValue) {
if (viewValue && viewValue !== original ) {
if(scope.isValidEmail()){
User.isUniqueEmail(viewValue).then(function(data){
ngModelCtrl.$setValidity('uniqueEmail', !data.email);
});
}
return viewValue;
}
});
scope.isValidEmail = function(){
return scope.form.email.$isvalid;
}
}
}
}
});
it still doesnt work the value of scope.form.email.$isvalid
is unreiable and seems out of date :(
The order of the parsers is important. I am not sure but your parser may have got registered before the inbuilt parsers and hence gets fired first.
Maybe if you splice the $parsers array and insert your parser at 0 it may work.