9

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>
6

3 Answers 3

1

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}}'
        };
    });
})();
Sign up to request clarification or add additional context in comments.

3 Comments

It may be more idiomatic angular to use a shared service rather than a directive to synchronize the error message.
Thanks for posting your solution but It also seems like a bad idea to do it this way... What if the ngModel is passed in after the myErrorHandler on the controllers array. What if you have multiple async validators? The amount of extra code just went way up.
The controllers array will be passed in the same order as the require array, so there's no need to worry about that. As for multiple validators, you could keep an object of error messages instead of a single string, much like $error works on the NgModelController.
1

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.

Comments

1

I would suggest do the global exception handling on server side if error occurs then send success as false with error message

json.put("success", false);
json.put("message",errorMessage);

Otherwise send json with success as true with value

json.put("success", true);
json.put("values",output);

Then on UI side handle exception using

$.post(serverUrl,valuesJson)
.success(function(responseData) {
    var responseData = JSON.parse(responseData);
    if(responseData.success){
        \\ write your logic
    } else {
        alert(responseData.message);
    }
}).fail(function(xhr, textStatus, errorThrown) {
    //check for session out
    if(xhr.status == 403) 
        alert( "Your session is timed out.redirected to Login screen." );
    // redirect to login    
})
.error(function(responseData){
    console.log(responseData);
});

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.