Skip to main content
Ben Nadel at CFCamp 2023 (Freising, Germany) with: Sebastian Zartner
Ben Nadel at CFCamp 2023 (Freising, Germany) with: Sebastian Zartner

Piping Global Errors Into The $exceptionHandler Service In AngularJS

By on

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 &lt;P&gt; 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:

Piping global errors into the $exceptionHandler service in AngularJS.

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

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel