13

I'm building an AngularJS SPA application with WebApi for the backend. I am using attributes for model validation on the server, if validation fails this is what I return from the ModelState.

     {"Message":"The request is invalid.","ModelState":{"model.LastName":["Last Name must be at least 2 characters long."]}}

How do I then render this to the client with AngularJS?

      //Save User Info
    $scope.processDriverForm = function(isValid) {
        if (isValid) {
            //set button disabled, icon, text
            $scope.locked = true;
            $scope.icon = 'fa fa-spinner fa-spin';
            $scope.buttonText = 'Saving...';
            $scope.submitted = true;
            $scope.formData.birthDate = $scope.formData.birthMonth + '/' + $scope.formData.birthDay + '/' + $scope.formData.birthYear;
            $http({
                    method: 'POST',
                    url: 'api/Account/Register',
                    data: $.param($scope.formData),
                    headers: { 'Content-Type': 'application/x-www-form-urlencoded' } // set the headers so angular passing info as form data (not request payload)
                })
                .success(function (data) {
                    console.log(data);
                    toastr.success('User ' + $scope.formData.username + ' created!');
                    $scope.userForm.$setPristine();
                    $scope.formData = {};
                    //reset the button
                    $scope.locked = false;
                    $scope.icon = '';
                    $scope.buttonText = 'Save';
                    //reset validation submitted
                    $scope.submitted = false;
                })
                .error(function (data, response) {
                    console.log(data);
                    toastr.error('Ooops! There was an error creating the user. Try again and if the problem persists, contact Support.');
                    //reset the button
                    $scope.locked = false;
                    $scope.icon = '';
                    $scope.buttonText = 'Save';
                    $scope.submitted = false;

                    var resp = {};

                    var errors = [];
                    for (var key in resp.ModelState) {
                        for (var i = 0; i < resp.ModelState[key].length; i++) {
                            errors.push(resp.ModelState[key][i]);
                        }
                    }
                    $scope.errors = errors;

                });

        }
        else {
            toastr.warning('Invalid User Form, correct errors and try again.');
        }
    };
Brad Martin
  • 5,637
  • 4
  • 28
  • 44
  • what have you tried ? You can handle this by dozens of methods.. This question is way to broad. First you have to choose the behavior of your app for dispaying them (alert ? popup ? sticky notification ?) ... – Jscti Apr 15 '14 at 15:10
  • Sorry, I didn't intend for the manner of rendering. I just was unsure how to get the errors out of the ModelState array that is passed back with the 400 BadRequest error – Brad Martin Apr 15 '14 at 15:37

2 Answers2

18

When making your call to your server, capture the error based upon the rejection of the $http promise.

Then in your controller I would suggest flattening the response to an array of errors upon handling of the error for display as shown in this fiddle example:

for (var key in resp.ModelState) {
    for (var i = 0; i < resp.ModelState[key].length; i++) {
        errors.push(resp.ModelState[key][i]);
    }
}

To put it all together:

// Post the data to the web api/service
$http.post(url, data)
    .success(successHandler)
    .error(function (response) {
        // when there's an error, parse the error
        // and set it to the scope (for binding)
        $scope.errors = parseErrors(response);
    });

//separate method for parsing errors into a single flat array
function parseErrors(response) {
    var errors = [];
    for (var key in response.ModelState) {
        for (var i = 0; i < response.ModelState[key].length; i++) {
            errors.push(response.ModelState[key][i]);
        }
    }
    return errors;
}
Brocco
  • 62,737
  • 12
  • 70
  • 76
  • I've added the $http request in my code, but how do I get those errors out of the BadRequest that is returned and put into the 'resp' variable. I'm still in the early stages of learning JS, only 2 months into this and it's my first exposure to JS and AngularJS. Is that what the .push is for? – Brad Martin Apr 15 '14 at 15:39
  • I updated the code in my answer to show a more complete solution. – Brocco Apr 15 '14 at 16:31
  • Good to go and I now understand the process. Great stuff. – Brad Martin Apr 15 '14 at 19:03
  • response.ModelState will be response.modelState - camel case right? web api will serialize it in camelcase I believe. – Austin Harris Mar 02 '17 at 22:02
3

The simplest way might be to grab all the errors from ModelState and put them into a new property on $scope.

$http.post(url, data).
    success(successHandler).
    error(function (response) {
        $scope.errors = getErrors(response);
    });

function getErrors(responseWithModelState) {
    var errors = [];
    /*
    Get error messages out of ModelState property, and push them into the errors variable...
    Brocco beat me to it. :-)
    */
    return errors;
};

Then in your HTML...

<ul>
    <li ng-repeat="e in errors">{{e}}</li>
</ul>

Or, instead of doing this in every error handler, you could write it once and have it apply to every HTTP request by using an interceptor. I've never written one myself, so I'll just point you to the doc (scroll down to the Interceptors section).

Andrew
  • 895
  • 5
  • 17
  • I've added my $http request but I'm unsure of how to complete this. @Brocco's fiddle is great except I'm unsure how to implement this. Any help with the code I added? – Brad Martin Apr 15 '14 at 16:09
  • It looks like @Brocco's update should help you. I don't have anything to add that he hasn't said. Good luck! :-) – Andrew Apr 15 '14 at 18:59