Piping Global Errors Into The $exceptionHandler Service In AngularJS
Out of the box, AngularJS has excellent error handling in so much as that everything that AngularJS "knowns about" is executed inside of a try/catch block. These managed errors are handed off to the $exceptionHandler service, where your application may or may not process them further. But, errors that happen outside of an AngularJS workflow, such as those that happen in a jQuery plugin, go off into the void. As such, I wanted to see if I could catch those unhandled errors and pipe them into the core $exceptionHandler service.
Run this demo in my JavaScript Demos project on GitHub.
The global error handler - window.onerror - is, from what I have read, a rather old and outdated feature and is not standardized across browsers. As such, if you are using a "professional" 3rd-party script that handles client-side errors, you are going to be better off just letting that script manage the errors. This exploration attempts to manage errors in the absence of such a script (though I do my best to not overwrite any existing window.onerror functionality).
Because we are trying to handle errors that happen outside of the AngularJS workflow, I think this functionality is best configured in a "run block" that executes after the AngularJS application is bootstrapped. You might be tempted to try to "decorate" the $window service during the configuration phase (my first attempt). However, you will run into a circular dependency-injection reference between $window, $exceptionHandler, and $log.
In the following code, I attach a new $window.onerror function which turns around and sends the error object to the $exceptionHandler service. The assumption with this approach is that the $exceptionHandler service is either being overridden or decorated by something else in the AngularJS application.
<!doctype html> | |
<html ng-app="Demo"> | |
<head> | |
<meta charset="utf-8" /> | |
<title> | |
Piping Global Errors Into The $exceptionHandler Service In AngularJS | |
</title> | |
</head> | |
<body> | |
<h1> | |
Piping Global Errors Into The $exceptionHandler Service In AngularJS | |
</h1> | |
<p bn-test> | |
Mousing over this <P> will cause an error in the console. | |
</p> | |
<!-- Load scripts. --> | |
<script type="text/javascript" src="../../vendor/angularjs/angular-1.3.16.min.js"></script> | |
<script type="text/javascript"> | |
// Create an application module for our demo. | |
var app = angular.module( "Demo", [] ); | |
// --------------------------------------------------------------------------- // | |
// --------------------------------------------------------------------------- // | |
// I am here to make sure our error handler doesn't override any existing error. | |
window.onerror = function( message ) { | |
console.log( "Original error handler:", message ); | |
return( true ); | |
}; | |
// When the app is bootstrapped, we want to wire into the global error handler | |
// so that we can pass "external errors" (ie, errors that happen outside the | |
// AngularJS work-flows) off to the $exceptionHandler where they may or may not | |
// be further processed. | |
// -- | |
// CAUTION: The global error handler is, apparently, a pile of inconsistent junk | |
// in different browsers. If you are using a 3rd-party script that handles global | |
// errors, it would probably be best to just let that script manage this stuff | |
// since it will likely do a better job. This demo assumes that you are NOT using | |
// something of that nature. | |
app.run( | |
function addGlobalErrorHandler( $window, $exceptionHandler ) { | |
// Get a reference to the original error handler, if it exists, so we | |
// don't overwrite any error-handling functionality that might be added | |
// by a 3rd-party script. | |
var originalErrorHandler = $window.onerror; | |
// If there is no existing global error handler, let's define a mock one | |
// so that our custom error handler code can be handled uniformly. | |
if ( ! originalErrorHandler ) { | |
originalErrorHandler = function mockHandler() { | |
// By returning True, we prevent the browser's default error handler. | |
return( true ); | |
}; | |
} | |
// Define our custom error handler that will pipe errors into the core | |
// $exceptionHandler service (where they may be further processed). | |
// -- | |
// NOTE: Only message, fileName, and lineNumber are standardized. | |
// columnNumber and error are not standardized values and will not be | |
// present in all browsers. That said, they appear to work in all of the | |
// browsers that "matter". | |
$window.onerror = function handleGlobalError( message, fileName, lineNumber, columnNumber, error ) { | |
// If this browser does not pass-in the original error object, let's | |
// create a new error object based on what we know. | |
if ( ! error ) { | |
error = new Error( message ); | |
// NOTE: These values are not standard, according to MDN. | |
error.fileName = fileName; | |
error.lineNumber = lineNumber; | |
error.columnNumber = ( columnNumber || 0 ); | |
} | |
// Pass the error off to our core error handler. | |
$exceptionHandler( error ); | |
// Pass of the error to the original error handler. | |
try { | |
return( originalErrorHandler.apply( $window, arguments ) ); | |
} catch ( applyError ) { | |
$exceptionHandler( applyError ); | |
} | |
}; | |
} | |
); | |
// I am a directive that will trigger an error outside of an AngularJS workflow. | |
app.directive( | |
"bnTest", | |
function() { | |
// Return the directive configuration object. | |
return({ | |
link: link, | |
restrict: "A" | |
}); | |
// I bind the JavaScript events to the local view-model. | |
function link( scope, element, attributes ) { | |
element.on( | |
"mouseover", | |
function handleMouseoverEvent( event ) { | |
// NOTE: This will break, hold on to your butts! | |
undefinedValue.doSomething(); | |
} | |
); | |
} | |
} | |
); | |
</script> | |
</body> | |
</html> |
When I mouse-over the problematic <P> element, the error is caught by my global error handler and passed off to the $exceptionHandler service, where it is [by default] logged to the console:

As you can see, the original error handler was left in tact even though we added our own error handling logic.
Again, you are probably going to be better off using a professional error handling vendor script. But, as a first step, this kind of approach will at least try to unify the way you are handling errors in your AngularJS application. And, at that point, you can decorate the $exceptionHandler service to track errors that happen both inside and outside of AngularJS workflows.
Want to use code from this post? Check out the license.
Reader Comments