Background:
On a project I am working on, we've switched from using AngularJS (1.6.2) with JavaScript, to TypeScript 2.1.5.
We have a decorator applied to the $exceptionHandler service that causes JavaScript exceptions to make a call to a common API that send the development team an e-mail; in this way, we can easily see what front-end errors our end-users are encountering in the wild.
Problem:
I recently converted this decorator from JavaScript, to TypeScript. When I try to run the application, I encounter a whitescreen of nothing. After much debugging I discovered that the issue is because AngularJS expects the $provide.decorator to pass a function along with a list of dependencies. However, an object is instead being observed, and thus forcing Angular to fail-safe.
I diagnosed the problem by setting breakpoints inside of angular.js itself; it specifically will fail on line 4809 (inside of function createInternalInjector(cache, factory)) due to a thrown, unhandled JavaScript exception, but the part that's actually responsible for the failure is line 4854, inside of function invoke(fn, self, locals, serviceName). The reason it fails, is because the dependencies passed come across as ['$delegate', '$injector',]; the function is missing from this set.
Lastly, one thing I considered doing was simply defining a JavaScript function in the class code. This does not work in my case for two reasons. First, in our ts.config, we have noImplicitAny set to true; functions are implicitly of the any type. Additionally, TypeScript itself appears not to recognize function as a keyword, and instead tries and fails to compile it as a symbol on class ExceptionHandler.
TypeScript Exception Handler:
export class ExceptionHandler {
public constructor(
$provide: ng.auto.IProviderService
) {
$provide.decorator('$exceptionHandler`, [
'$delegate',
'$injector',
this.dispatchErrorEmail
]);
}
public dispatchErrorEmail(
$delegate: ng.IExceptionHandlerService,
$injector: ng.auto.IInjectorService
): (exception: any, cause: any) => void {
return (exception: any, cause: any) => {
// First, execute the default implementation.
$delegate(exception, cause);
// Get our Web Data Service, an $http wrapper, injected...
let webDataSvc: WebDataSvc = $injector.get<WebDataSvc>('webDataSvc');
// Tell the server to dispatch an email to the dev team.
let args: Object = {
'exception': exception
};
webDataSvc.get('/api/common/errorNotification', args);
};
}
}
angular.module('app').config(['$provide', ExceptionHandler]);
Original JS:
(function () {
'use strict';
angular.module('app').config(['$provide', decorateExceptionHandler]);
function decorateExceptionHandler($provide) {
$provide.decorator('$exceptionHandler', ['$delegate', '$injector', dispatchErrorEmail]);
}
function dispatchErrorEmail($delegate, $injector) {
return function (exception, cause) {
// Execute default implementation.
$delegate(exception, cause);
var webDataSvc = $injector.get('webDataSvc');
var args = {
'exception': exception,
};
webDataSvc.get('/api/common/ErrorNotification', args);
};
}
})();
Questions:
1. In what way can I rewrite the TypeScript Exception Handler to be properly picked up by AngularJS?
2. If I can't, is this an AngularJS bug that needs to be escalated? I know for a fact I'm not the only person using AngularJS with TypeScript; being unable to decorate a service due to language choice seems like a pretty major problem.
configexpects a callback function, not a constructor. And$exceptionHandleris factory function, not a class, too. There's just no place for classes.