Better Error Handling In AngularJS

Monday, April 21, 2014

In my recent work I’ve been using two approaches to handling errors and exceptions. The ultimate goal is to not let an error go unnoticed.

$exceptionHandler

First up is a decorator for the $exceptionHandler service. We’ve looked at other decorators in a previous post. This specific decorator will send all errors to $rootScope for data binding before allowing the call to fall through to the default implementation (addError is a custom method on $rootScope, while $delegate represents the service being decorated). You could also try to send the errors back to the host and thereby collect errors from all clients.

app.config(function($provide){

    $provide.decorator("$exceptionHandler", function($delegate, $injector){
        return function(exception, cause){
            var $rootScope = $injector.get("$rootScope");
            $rootScope.addError({message:"Exception", reason:exception});
            $delegate(exception, cause);
        };
    });

});

Notice the use of $injector in the above code. Using the $injector service directly is required to avoid a circular dependency error by advertising both $exceptionHandler and $rootScope as dependencies.

Promising Errors

I’m a fan of using catch at the end of a chain of promises. One reason is that catch is the only sure fire way to process all possible errors. Let’s use the following code as an example. 

someService
    .doWork()
    .then(workComplete, workError);

Even though an error handler (workError) is provided to the then method, the error handler doesn’t help if something goes wrong inside of workComplete itself . . .

var workComplete = function(result){
    return  $q.reject("Feeling lazy");
};

. . . because we are already inside a success handler for the previous promise. I like the catch approach because it handles this scenario and also makes it easier to see that an error handler is in place.

someService
    .doWork()
    .then(workComplete)               
    .catch(errors.catch("Could not complete work!"));

Since so many catch handlers started to look alike, I made an errors service to encapsulate some of the common logic.

app.factory("errors", function($rootScope){
    return {
        catch: function(message){
            return function(reason){
                $rootScope.addError({message: message, reason: reason})
            };
        }
    };
});

And now async related activities can present meaningful error messages to the user when an operation fails.


Comments
gravatar Gleb Monday, April 28, 2014
Good example, I also wrote about catching errors in angular and forwarding them to Sentry http://bahmutov.calepin.co/catch-all-errors-in-angular-app.html There is also a post on error handling in promises in general http://bahmutov.calepin.co/error-handling-in-promises.html Gleb
gravatar Eder Wednesday, May 21, 2014
Hi, I found this post quite useful but I'd like to know if there is any way to send also success messages, for example: I am trying to call the errors service like this: someOtherService.getSomething.then(errors.catch("SUCCESS")); but it doesn't seem to work and can't find a way to make the service work this way. Is the service not supposed to be used this way? Thanks for the post!
gravatar Eder Wednesday, May 21, 2014
Finally got it, I am using a modified version of the error service. I have a directive watching for $rootScope.messages, I just modified the service like this: app.factory("errors", function($rootScope){ return { catch: function(message){ $rootScope.messages = {message: message}; } }; }); Since for some reason the function inside the catch() was never being called.
gravatar Abhishek Shukla Friday, May 23, 2014
Hi Scott, First, I found the article very helpful. But, I have one question, instead of using $injector for loading $rootScope, this can be injected as a dependency also. Any specific reason for doing this way. Thanks
gravatar Scott Saturday, May 24, 2014
@Abhishek: 3rd paragraph -> Notice the use of $injector in the above code. Using the $injector service directly is required to avoid a circular dependency error by advertising both $exceptionHandler and $rootScope as dependencies.
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!