Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to render errors to client? AngularJS/WebApi ModelState

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.');
        }
    };
like image 426
Brad Martin Avatar asked Apr 15 '14 14:04

Brad Martin


2 Answers

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;
}
like image 98
Brocco Avatar answered Oct 30 '22 05:10

Brocco


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).

like image 20
Andrew Avatar answered Oct 30 '22 05:10

Andrew