OdeToCode IC Logo

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.