Wait on $asyncValidators to submit form - angularjs

I have a form that has a username field and other fields, this field has an async validator that checks whether that username is available when you try to submit the form (there are other fields on the form using async validation). I use this directive to validate and submit the form (only if it's valid):
<form validation-submit="submit()"></form>
app.directive('validationSubmit', ['$parse', function($parse) {
return {
restrict: 'A',
require: '?form',
compile: function($element, attr) {
var submitFn = $parse(attr.validationSubmit);
return function link(scope, element, attrs, FormController) {
var submit = function(event) {
scope.$apply(function() {
if (! FormController.$valid) {
return;
}
submitFn(scope, {$event: event}));
});
};
element.bind('submit', submit);
scope.$on('$destroy', function() {
return element.off('submit', submit);
});
}
}
};
}]);
The problem is this directive is not waiting for the pending async validations to finish. How can I change this directive to only submit after all async validations finished and passed?

Recently I've created a couple of directives that were checking whether email or phone number is available. I found solution that helps me. It's $asyncValidators .
app.directive('validationSubmit', ['$parse', function($parse) {
return {
restrict: 'A',
require: '?form',
link: function(scope, element, attrs, FormController) {
/**
*returns promise
**/
FormController.$asyncValidators.validEmail = function (modelValue) {
return $q(function (resolve, reject) {
//Here you should make query to your server and find out wether username valid or not
//For example it could be:
$http('http://your_api_server/Available').then(function (response) {
if (response.data.Available) {
resolve();
} else {
reject();
}
}, function (err) {
reject(err.data);
});
});
};
}
};
}]);
You should add attribute name to the form:
<form validation-submit="submit()" name="myForm"></form>
And now you have opportunity to check system properties of form in your controller:
$scope.submit = function(){
//Here will be your logic
if($scope.myForm.$valid ||
$scope.myForm.$invalid ||
$scope.myForm.$pending ||
$scope.myForm.$submitted){
//do whatever you want
}
}

Related

Trigger click to activate typeahead after element gets focus

Need help with several issues. Here is simplified code:
HTML
<input type="text" title="{{e.Name}}" ng-model="e.modelName" ng-required="true" typeahead-editable="false" ng-blur="vm.isUnchanged(i)" focus-me="vm.Event"
own-typeahead typeahead-on-select="vm.changeValue($item, $model, $label, i)"
uib-typeahead="$Event, $viewValue)" typeahead-min-length="0"/>
JS
app.directive("ownTypeahead", function() {
var directive = {
link: link,
scope: true,
restrict: 'A',
require: ["ngModel"]
};
return directive;
function link(scope, element, attrs, ctrls) {
element.bind('click', function () {
if (ctrls[0].$viewValue && ctrls[0].$viewValue == ' ') {
ctrls[0].$setViewValue('');
}
ctrls[0].$setViewValue(' ');
});
element.bind('focus', function () {
if (!ctrls[0].$viewValue || ctrls[0].$viewValue == '') {
ctrls[0].$setViewValue(' ');
}
});
}
});
/**
* Directive that places focus on the element it is applied to when the
* expression it binds to evaluates to true
*/
app.directive('focusMe', ['$timeout', function focusMe($timeout) {
return function (scope, elem, attrs) {
scope.$watch(attrs.focusMe, function (newVal) {
if (newVal) {
$timeout(function () {
elem[0].focus();
}, 0, false);
}
});
};
}]);
The problems/questions are:
1) The main one. Focus after clicking on certain items triggers typeahead ddl in the input field almost always, but there are several items that move focus but don't trigger the list to be opened. Any ideas where is the issue? (the code above works in about 90% of the cases and in 100% with a click on input field)
2) Not ideal solution, but an ok workaround could be trigger a click event on the focused input field to open the list. Can't manage to get that right the angular way. How this could be done?
Got it working by adding a 200ms timeout for the focusMe:
app.directive('focusMe', ['$timeout', function focusMe($timeout) {
return function (scope, elem, attrs) {
scope.$watch(attrs.focusMe, function (newVal) {
if (newVal) {
$timeout(function () {
elem[0].focus();
}, 200, false);
}
});
};
}]);
If anyone has better suggestions would accept the answer.

how to display object in json formate in angular js?

I am trying to display value of all my fields in a json object .I am able to add firstname ,email , password in an object.but my confirm password not displaying in object why ? I enter same password with confirm password still not display
here is my code
http://plnkr.co/edit/iHA8iQC1HM5OzZyIg4p3?p=preview
angular.module('app', ['ionic','ngMessages']).directive('compareTo',function(){
return {
require: "ngModel",
scope: {
otherModelValue: "=compareTo"
},
link: function(scope, element, attributes, ngModel) {
ngModel.$validators.compareTo = function(modelValue) {
// alert(modelValue == scope.otherModelValue)
return modelValue == scope.otherModelValue;
};
scope.$watch("otherModelValue", function() {
ngModel.$validate();
});
}
};
why confirm password not display ?
}).controller('first',function($scope){
})
Your compareTo directive fails and it will not bind to a model if the validator is failing. If you remove your compareTo directive from the code you will get the confiredpassword in your scope.
Refer to this: password-check directive in angularjs to fix your comparTo directive.
Also here is a plunker of the fixed directive:
http://plnkr.co/edit/wM3r6eR2jhQS7cjvreLo?p=preview
.directive('compareTo', function() {
return {
scope: {
targetModel: '=compareTo'
},
require: 'ngModel',
link: function postLink(scope, element, attrs, ctrl) {
var compare = function() {
var e1 = element.val();
var e2 = scope.targetModel;
if (e2 !== null) {
return e1 === e2;
}
return false;
};
scope.$watch(compare, function(newValue) {
ctrl.$setValidity('errorCompareTo', newValue);
});
}
};

Accessing scope variable in directive

At the moment I have this directive that checks if the username entered by the user already exists in the database -
app.directive('usernameAvailable', function ($timeout, $q, userService, authService) {
return {
restrict: 'AE',
require: 'ngModel',
link: function (scope, elm, attr, model) {
model.$asyncValidators.Username = function () {
var User = {
Username: model.$viewValue
}
var defer = $q.defer();
var promiseGet = userService.DoesUsernameExist(User);
promiseGet.then(function (result) {
scope.$emit('updateUsername', model.$viewValue);
if (result.data == true) {
model.$setValidity('Username', false);
} else {
model.$setValidity('Username', true);
}
},
function (errorResult) {
console.log('Unable to check if the username exists', errorResult);
});
return defer.promise;
};
}
}
});
This works, but my issue is that when you try to edit a user it will say that the username is already taken since it already exists in the database. I want to pass a variable ($scope.InitialUsername) so that i can put a condition in my directive that the username is allowed to equal this when editing a user.
This will also mean passing in a variable for editmode to allow this condition to be checked.
Take a look at this example
Any directive does have a scope variable where you can bind specific informations to
.directive('myDirective', function() {
return {
restrict: 'AE',
scope: {
initialUserName: '=name'
},
};
})
And in your html you can access this property like this:
<my-directive name="tom"></my-directive>
And on the template side you can query the name like
{{initialUserName.name}}

Async function in $parsers doesn't update $modelValue

Was wondering how I should handle async functions in $parsers.
The below code doesn't update the scope.
I'm using AngularJS 1.2 so can't make use of the new and fancy 1.3 features.
http://plnkr.co/edit/uk9VMipYNphzk8l7p9iZ?p=preview
Markup:
<input type="text" name="test" ng-model="test" parse>
Directive:
app.directive('parse', function($timeout) {
return {
require: 'ngModel',
link: function(scope, element, attrs, ctrl) {
ctrl.$parsers.unshift(function(viewValue) {
$timeout(function() {
return viewValue;
});
});
}
};
});
If you are looking for async validation function, I did something like that some time ago and release it as a library. Check the custom-remote-validator directive here.
The basic idea was use ngModelController $setValidity after receiving validation result from server. This is the directive source code
.directive('customRemoteValidator', [function () {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, elm, attr, ngModelCtrl) {
var validateFunctionNames = attr["remoteValidateFunctions"].split(",");
var validatorNames = attr["customRemoteValidator"].split(",");
ngModelCtrl.$parsers.push(function (value) {
angular.forEach(validateFunctionNames, function (functionName, index) {
if (!scope[functionName]) {
console.log('There is no function with ' + functionName + ' available on the scope. Please make sure the function exists on current scope or its parent.');
} else {
var result = scope[functionName](value);
if (result.then) {
result.then(function (data) { //For promise type result object
ngModelCtrl.$setValidity(validatorNames[index], data);
}, function (error) {
ngModelCtrl.$setValidity(validatorNames[index], false);
});
}
}
});
return value;
});
}
};
}])

AngularJS validity not reset once $setValidity called

I have this element:
<input type="text" name="azurerepo"
ng-model="item.azurerepo"
ng-class="{error: myForm.azurerepo.$invalid}"
ng-required="item.deploymentType=='azure'"
ui-event="{ blur : 'azureCallback()' }" />
The callback does:
$scope.myForm.azurerepo.$setValidity('azurerepo',false);
If I type data and come out of the input it sets it invalid.
If I go back into the input, backspace all the entered data and then type something its still invalid! I would expect it to be valid now because data has been typed in.
I don't know why you decided to use angular-ui instead of creating simple directive, nevertheless I suppose it's possible to add keyup event to ui-event directive and call function to set validity true here.
But I'd rather recommend you to keep it simple with custom directive:
yourApp.directive('checker', function () {
return {
restrict: 'A',
scope: {
checkValidity: '=checkValidity' // isolate directive's scope and inherit only checking function from parent's one
},
require: 'ngModel', // controller to be passed into directive linking function
link: function (scope, elem, attr, ctrl) {
var yourFieldName = elem.attr('name');
// check validity on field blur
elem.bind('blur', function () {
scope.checkValidity(elem.val(), function (res) {
if (res.valid) {
ctrl.$setValidity(yourFieldName, true);
} else {
ctrl.$setValidity(yourFieldName, false);
}
});
});
// set "valid" by default on typing
elem.bind('keyup', function () {
ctrl.$setValidity(yourFieldName, true);
});
}
};
});
and your element:
<input name="yourFieldName" checker="scope.checkValidity" ng-model="model.name" ng-required=... etc>
and controller's checker itself:
function YourFormController ($scope, $http) {
...
$scope.checkValidity = function (fieldValue, callback) {
$http.post('/yourUrl', { data: fieldValue }).success(function (res) {
return callback(res);
});
};
...
}

Resources