-
Notifications
You must be signed in to change notification settings - Fork 27.4k
Add a way to handle $asyncValidators completion #10768
Comments
We are working on a bigger refactor of
|
The ngModel tweak and the return of a promise from the validation function are fine if you just care about knowing when a single async validator is finished. (EDIT: I really mean a single field, since a field can have multiple async validators.) But I'm working at the level of the overall form, so I need to know when all of the async validators (for all the fields in the form) are done. That's why I was thinking that the $pending object itself could have a promise key, to store a Promise that resolves once all of the async validators are finished. Hopefully this makes sense. |
@petebacondarwin I think this is something we should keep in mind for the ngModel refactor / as a second step. |
This is in my POC. For a single validation to complete see: https://github.com/petebacondarwin/ngModelPOC/blob/master/src/Validity.js#L18-L58 |
I think that looks good. Can I infer that the promise will end up as a key on the form's $pending object? |
This is up for grabs. We should have a discussion about how best to represent this. I am thinking more explicit |
Hmm...from what I can see, the existing $pending object contains keys for each of the async validators in use, correct? If so, I'd say just adding another key to contain the promise would seem to make sense, vs. adding a $pendingValidation to the form object. (At least I'm assuming it would go there, alongside $pending?) That said, I really don't have a strong preference either way. As long as I have a stable way to know when the async validators are finished, I'll be happy. |
For now, can't you just place a watcher on |
As I said in my initial post, that's what I'm doing now. It's just not a very simple/obvious/intuitive solution for something this fundamental to form validation. Since the async validation functions already return promises, wrapping them up inside another promise should be pretty straightforward. It's also much more elegant and in line with how async processes should be handled in general. This is what promises are for, after all. :-) |
+1 to this. At the moment I have $scope.next = function(form)
{
if (form.$valid) {
$scope.process.step += 1;
}
}; But as said above, the form might be in the process of resolving it's asyncValidators, so $pending contains stuff. Maybe it would be cool if I could do something like: $scope.next = function(form)
{
form.$asyncValid.then(function() {
$scope.process.step += 1;
});
}; Right now I'm doing: $scope.next = function(form)
{
if (form.$pending) {
var pendingWatch = $scope.$watch(function() {
return form.$pending;
}, function(pending) {
if (!pending) {
pendingWatch();
$scope.next(form);
}
});
}
if (form.$valid) {
$scope.process.step += 1;
}
}; |
@intellix +1 |
@intellix - actually I am concerned that this design of your app would add complexity and cause the user problems. For example, if the user clicks submit but that the async validation fails then you would have to:
It would seem to me much simpler to set form submission to disabled while |
I have the loading/thinking state handled as well but didn't include it inside the snippet. I guess what you're saying is to just outright disable form submission on pending. I could do that instead :) |
That is what I mean. That removes the need for all this logic. |
Ok, the last comment is from over a year ago. Is there any stable solution for this? I just wrote an entire piece of code to handle the scenario just to find out that what ngModel.$promise contains is just Boolean values. I thought they were the promises returned by the validators! Anyway, I shall leave my code here in case it serves as inspiration for improvement. My code assumes that ngModel.$pending will contain key/value pairs of type validationKey/promise. The second the promise resolves, the pair is removed from $pending. Basically I am creating a deferred object that will be resolved as soon as a validator deems the value invalid, or after all pending validators finish. I guess this logic can be used for the fom/ngForm controller to provide the promise of this deferred object somehow to the consumer. var deferred = ngQ.defer();
var totalAsync = 0;
var totalRemaining = 0;
ng.forEach(this.Data.Form.$pending, function(controls) {
ng.forEach(controls, function(control) {
ng.forEach(control.$pending, function(promise) {
var index = totalAsync++;
c.log('Promise: %o', promise);
promise.then(function() {
deferred.notify({ index: index, result: true });
if (--totalRemaining <= 0) {
deferred.resolve();
}
}
, function() {
deferred.notify({ index: index, result: false });
deferred.reject();
});
});
});
}); EDIT In case anybody like me stumbles upon this thread by looking for a solution, I worked around the issue by encapsulating an interval check on form/ngForm.$pending. Once the object has no controls listed the deferred object is resolved; if the specified timeout elapses, the deferred object is rejected. I share here as well. Oh, and it is worth mentioning that I have this in an attribute-only directive that extends the form/ngForm controller with the method. And I know you hardcore JavaScripters like camelCase, but I am a strong .net player and I like my PascalCase. :-) function _WaitOnPendingValidators(abortTimeout) {
var deferred = ngQ.defer();
var form = this;
var totalAsyncFn = function() {
var totalAsync = 0;
ng.forEach(form.$pending, function(controls) {
++totalAsync;
});
return totalAsync;
}
//If there are no async validators pending, resolve immediately.
if (totalAsyncFn() == 0) {
deferred.resolve();
} else {
var intervalPromise = interval(function() {
var total = totalAsyncFn();
deferred.notify(total);
if (total == 0) {
interval.cancel(intervalPromise);
deferred.resolve();
}
}, 250);
intervalPromise.then(null, null, function(times) {
c.log('Checking for pending asynchronous validators. Run # %i.', times);
});
if (abortTimeout && abortTimeout > 0) {
timeout(function() {
//Abort operation.
interval.cancel(intervalPromise);
deferred.reject();
}, abortTimeout);
}
}
return deferred.promise;
} I attach this function to the form controller and therefore the this pointer is the form controller. I do not like using $ for my variables so the ngQ you see there is the $q service. Also c is window.console. I inject it to the IIFE for quick access. I also use $interval and $timeout in the variables interval and timeout respectively. |
I edited your comment @webJose - you needed to use triple backticks for a block of code. |
@petebacondarwin , thanks for the help. Do you happen to know if there is a solution for this already? Maybe I need to move to a newer AngularJS? I have been avoiding this in my current project because there is so much else to do to add, on top of all, potential errors introduced by moving to a new version. My current version is 1.5.3, and the guy who did this project only knew that AngularJS existed, that it had ng-model and that it could show or hide stuff. That is it. All AngularJS code written in a single file for 6 different HTML pages, manually fighting model changes and validation changes. Almost 9000 lines of code. My first cleanup reduced the size by half but I am a long way to go still. :-( |
There is no generic solution built into any version of AngularJS at this time. Sorry @webJose. What you are doing appears to be a reasonable approach for your app. |
The new $asyncValidators are a nice addition. But there seems to be a critical missing piece here: there doesn't seem to be a way to react when the $asyncValidators complete their work.
The documentation and blog entries I've been able to find will occasionally mention that you can add a check for $pending to a submit button to prevent submission. But that seems to be a really constrained option, since it offers no programmatic control.
The only option I could come up with was to check if $pending is present on the form scope, and add a watch on it. When $pending is removed, I can clear the watch and trigger additional logic. This works, but it seems harder than it should be.
My thought is to add a "promise" key to the $pending object, that provides a Promise around the async validation logic. This way, we would be able to do $pending.promise.then( ... ) to react when the async validation is complete.
The text was updated successfully, but these errors were encountered: