Please enable JavaScript to view the comments powered by Disqus.

Customize Angular Exception Handler

Posted on July 18, 2015, by markocen

About me Blog Project Contacts

In Angular, all exceptions would be delegated to exceptionHandler service. By default, exceptionHandler only log exception to console. However, in production, we may need more features rather than logging. This post demonstrates a simple way to build a rich-feature customized Angular exception service.

Why Rich-feature Error Handler ?

Recently, I am working on a complex rich-client web application which using Angular as its front-end framework. This application contains hundreds of controllers, directives and services. For such a huge application, even you have did thousand times of testing, you can still expect error happened when it is in production. So what we can do when an exception occurred on customer side? We can’t tell a customer to press F12, and check the logs, we need a more friendly way to display that error to our customer, and provide a more convenient way to let customer to handler that error. All cases above make us to build rich-feature error handler.

What a Rich-feature Error Handler Would Do ?

In this post, we want to build an Angular error handler which has following features :

  • Can terminate application running when a fatal exception happened, and popup a alert box to display that error, therefore, user can know the reason why application stopped.
  • Has an error stack to record all errors.
  • Can submit an exception information or error stack to server.
  • Has two modes : debug mode or production mode, in debug mode, every exception would be treat as fatal exception which can stop application running, in production mode, only defined fatal exceptions can stop application running.

Overwrite Default $exceptionHandler

First, we want to overwrite Angular $exceptionHandler service by our own service. This will make sure every exception happened in Angular would invoke our service, even no try... catch... added :

var demo = angular.module('errorHandlingDemo', []);

demo.factory('exceptionSrvc', [function(){

    // ... implemented logic

}]);

demo.provider('$exceptionHandler', ['exceptionSrvc', function(exceptionSrvc){

    return exceptionSrvc;

}])

The overwriting is pretty straightforward: we create a service factory called exceptionSrvc, then we use it as the provider of $exceptionHandler .

Implement Exception Service

Till now, we have create our service and use it in $exceptionHandler, the last step is to implement the service :

demo.factory('exceptionSrvc',

['$log', '$injector', '$http', function($log, $injector, $http){

    // determine if this service in debug mode or production mode
    var DEBUG_MODE = true;

    // set all exceptions to fatal default
    var FATAL_ERROR = true;

    // store all exceptions
    var _errorQueue = [];

    // this function would insert an alert box on web page which contain 
    // error infos
    var displayException = function(ex){

        var $$rootScope = $injector.get('$rootScope');
        var $$compile = $injector.get('$compile');

        var dialogHtml = '<div id="alert-dialog">' +
                         '<p>App stopped due to the following error</p>'+
                         '<p>'+ex.toString() + '</p>' +
                         '<button ng-click="closeDialog()">Cancel</button>' +
                         '<button ng-click="submitError()">Submit</button>' +
                         '</div>';

        var scope = $$rootScope.$new();

        scope.closeDialog = function(){

            angular.element(document.querySelector('#alert-dialog')).remove();
            scope.$destroy();

        };

        scope.submitError = function(){

            sendErrorToServer().then(function(){

                scope.closeDialog();

            })

        };

        angular.element(document.body).append($$compile(dialogHtml)(scope));

        //throw this exception to upper catch block
        throw ex;
    };

    // this method would send error stack to a log server
    var sendErrorToServer = function(){

        return $http.post('your_url', _errorQueue);

    };

    // this method would return an customized exception object
    var buildErrorObject = function(ex, cause, isFatal){
        return {
            errorMessage: ex.toString(),
            errorObj: ex,
            errorCause: cause,
            timestamp: Date.now(),
            isFatal: isFatal
        };
    };

    /**
     * @ex {Object} : Error exception object
     * @cause {String} : A string describe this exception
     * @isFatal {Boolean} : determine if this exception is fatal
     */
    var exceptionSrvc = function(ex, cause, isFatal){

        var isFatalError = (isFatal === undefined) ? FATAL_ERROR : isFatal;

        var errorObject = buildErrorObject(ex, cause, isFatal);

        _errorQueue.push(errorObject);

        if(DEBUG_MODE){

            displayException(ex);

        }else{

            if(isFatalError){

                displayException(ex);

            }else{

                $log.error(errorObject);

            };

        };
    }

    return exceptionSrvc;
}]

)

Manually Delegate Exception

Although $exceptionHandler implement our exceptionSrvc, we still want to handle a exception manually by adding try...catch... in our code :

demo.controller('userCtrl',
    ['$scope', 'exceptionSrvc', function($scope, exceptionSrvc){

        $scope.findUserInfo = function(){

            try{

                // ...

            }catch(ex){

                exceptionSrvc(ex, 'something is wrong', true);

            }

        };

    }]
);