Consider a form invalid while async validator promises are pending
Asked Answered
P

1

18

I have an async validator:

app.directive('validateBar', ['$http', function($http) {
    function link($scope, $element, $attrs, ngModel) {
        ngModel.$asyncValidators.myValidator = function(value) {
            return $http.get('/endpoint/' + value);
        };
    }
    return {
        require: 'ngModel',
        link: link
    };
}]);

Form template:

<form name="myForm" ng-submit="foo.$valid && saveForm()">
    <input name="bar" ng-model="bar" data-validate-bar>
    <p ng-show="myForm.bar.$error.myValidator">Your bar is wrong</p>
    <button disabled="myForm.$invalid">
</form>

Problem: I want my accompanying form to be invalid while the myValidator promise is pending.

I know two ways to invalidate a form while async validators are pending, but they're both verbose and/or hacky.

// Workaround 1: Mark another validator as invalid while the validator promise is pending.
// I cannot mark 'myValidator' as invalid, gets set to valid immediately by Angular.
app.directive('validateSomething', ['$http', function($http) {
    function link($scope, $element, $attrs, ngModel) {
        ngModel.$setValidity('async', false);
        ngModel.$asyncValidators.myValidator = function(value) {
            return $http.get('/endpoint/' + value).then(function() {
                 ngModel.$setValidity('async', true);
            });
        };
    }
    return {
        require: 'ngModel',
        link: link
    };
}]);

<!-- Workaround 2: Prevent submitting through the UI -->
<form name="myForm" ng-submit="myForm.$valid && !myForm.$pending && saveForm()">
    <input name="bar" ng-model="bar" data-validate-bar>
    <p ng-show="myForm.bar.$error.myValidator">Your bar is wrong</p>
    <button disabled="myForm.$invalid || myForm.$pending">
</form>

I don't like workaround 1 because I mark another validator (async) as invalid, which may have unintended side effects, and I don't like workaround 2 because I can no longer trust form.$valid by itself.

Does anyone know a clean solution?

Predella answered 17/11, 2014 at 17:47 Comment(5)
I'm not sure how to best answer your question, but I just want to share my approach in case it helps. Demo here: m59peacemaker.github.io/angular-pmkr-components/#/… and directive here: github.com/m59peacemaker/angular-pmkr-components/tree/master/…Goiter
I think you're using a solution similar to workaround 2, as you're checking for pending in your view.Predella
I understand why you don't want to hack the framework's form validation. I think you're trying too hard to use Form Validity for multiple purposes. You should have 2 different forms of valid: Form Valid by UI rules, and Async data validation valid which should be a scope variable flag. The next question is what UI affect do you want to accompany the AsyncFlag = invalid. If none, and you only want it for submit allowance, then you can simply disable the submit button while your async validator promise is being resolved.Stoneham
So the documentation states that When the asynchronous validators are triggered, each of the validators will run in parallel and the model value will only be updated once all validators have been fulfilled. As long as an asynchronous validator is unfulfilled, its key will be added to the controllers $pending property. Also, all asynchronous validators will only run once all synchronous validators have passed. So I guess the "right way" to do it would be by depending on the $pending property. Haven't tried it in code yet.Djokjakarta
I'd probably just rely on myForm.$valid and myForm.$pending.length if I know I'm using async validators. I also found this example in the official forms documentation using $pendingDjokjakarta
E
28

You can use $pending to test if some async validator is pending on the whole form or a specific input element. I also added a test on $pristine to hide error messages when the page loads and used ng-disabled instead of disabled on the button.

<form name="myForm" ng-submit="foo.$valid && saveForm()">
    <input name="bar" ng-model="bar" data-validate-bar>
    <div ng-show="! myForm.$pristine">
      <p ng-show="myForm.bar.$pending.myValidator || myForm.bar.$error.myValidator">Your bar is wrong</p>
    </div>
    <button ng-disabled="myForm.$invalid || myForm.$pending">Do smth</button>
</form>
Efface answered 10/5, 2015 at 3:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.