Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS async validator - UI validation messages from server response

I have an async validator that calls a service will return 200 (OK) or 400 (BadRequest) with a message.

I'd like to return the message as the validation message but can't figure out how to get the message to show up. I've tried a few things without success.

<div ng-messages="searchFilterForm.search.$error">
  <small ng-message="valid">Invalid search filter. Reason: {{MY_BAD_REQUEST_RESPONSE_MESSAGE_GOES_HERE}}</small>
</div>
like image 848
Blake Niemyjski Avatar asked Dec 31 '14 16:12

Blake Niemyjski


2 Answers

Here's a jsfiddle: http://jsfiddle.net/javajunkie314/r6oyLe26/3/

The idea is that the myValidator and myErrorMessage directives synchronize their error message via the myErrorHandler directive. Annoyingly, I can't find any way to access the reason passed to reject by the validator.

Edit: I've updated the fiddle. Now myValidator uses two asynchronous validators. I've also created a ValidatorPromise to handle updating the myErrorHandler controller. This is as I described in my comment below.

The HTML:

<form ng-controller="testCtrl">
    <div my-error-handler="">
        <input type="text" name="foo" ng-model="foo.text" my-validator="">
        <div my-error-message=""></div>
    </div>
</form>

The JavaScript:

(function () {
    var app = angular.module('myApp', []);

    /* Wraps a promise. Used to update the errorHandler controller when the
     * validator resolves. */
    app.factory('ValidatorPromise', function ($q) {
        return function (errorHandler, name, promise) {
            return promise.then(
                // Success
                function (value) {
                    // No error message for success.
                    delete errorHandler.error[name];
                    return value;
                },
                // Failure
                function (value) {
                    // Set the error message for failure.
                    errorHandler.error[name] = value;
                    return $q.reject(value);
                }
            );
        };
    });

    app.controller('testCtrl', function ($scope) {
        $scope.foo = {
            text: ''
        };
    });

    app.directive('myErrorHandler', function () {
        return {
            controller: function () {
                this.error = {};
            }
        };
    });

    app.directive('myValidator', function ($timeout, $q, ValidatorPromise) {
        return {
            require: ['ngModel', '^myErrorHandler'],
            link: function (scope, element, attrs, controllers) {
                var ngModel = controllers[0];
                var myErrorHandler = controllers[1];

                ngModel.$asyncValidators.test1 = function () {
                    return ValidatorPromise(
                        myErrorHandler, 'test1',
                        $timeout(function () {}, 1000).then(function () {
                            return $q.reject('Fail 1!');
                        })
                    );
                };

                ngModel.$asyncValidators.test2 = function () {
                    return ValidatorPromise(
                        myErrorHandler, 'test2',
                        $timeout(function () {}, 2000).then(function () {
                            return $q.reject('Fail 2!');
                        })
                    );
                };
            }
        };
    });

    app.directive('myErrorMessage', function () {
        return {
            require: '^myErrorHandler',
            link: function (scope, element, attrs, myErrorHandler) {
                scope.error = myErrorHandler.error;
            },
            /* This template could use ngMessages to display the errors
             * nicely. */
            template: 'Error: {{error}}'
        };
    });
})();
like image 108
Chris Bouchard Avatar answered Oct 15 '22 13:10

Chris Bouchard


I prepared a plunker that I think do what you want. It uses a modified version of ui-validate but I think you can get the idea anyway.

I defined service called 'remoteValidator'. It has a method 'validate' that simulates the trip to the server returning a promise that rejects with a message if the value is 'bad'.

app.service("remoteValidator", function($q, $timeout) {
  this.validate = function(value) {
    return $q(function(resolve, reject) {
      setTimeout(function() {
        if (value === "bad") {
          reject("Value is bad");
        } else {
          resolve();
        }
      }, 1000);
    });
  }
});

Then I defined in the controller a validator that use that service and capture the error message and return the promise in order to your validator of choice do the work.

  $scope.remoteValAsync = function(value) {
    var promise = remoteValidator.validate(value);
    promise.then(function() {
      delete $scope.errorMsg;
    }, function(error) {
      $scope.errorMsg = error;
    });
    return promise;
  };

This way, when you write 'bad' in the async inputbox, when promises resolve, error message appears in the scope along with the validators name inside form.field.$error list.

A snippet of the html:

<div class="input-group-inline">
    <input class="form-control" type="text" name="nextPassword" ng-model="passwordForm.nextPassword" 
           ui-validate-async="{ badValue: 'remoteValAsync($value)' }" />
      <p>Pending validations:</p>
          <ul>
             <li ng-repeat="(key, errors) in myForm.nextPassword.$pending track by $index">{{ key }}</li>
          </ul>
      <p>Bad validations:</p>
          <ul>
             <li ng-repeat="(key, errors) in myForm.nextPassword.$error track by $index">{{ key }}</li>
          </ul>
      <p>{{ errorMsg }}</p>
</div>

I know droping the message in 'errorMsg' is not the best. It could be made better but I have to get in into de ui-validate-async directive.

I hope it helps.

like image 30
Emiliano Avatar answered Oct 15 '22 14:10

Emiliano