简体   繁体   中英

Cannot decorate AngularJS' ExceptionHandler using TypeScript, as a function isn't recognized as such

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.

Converting everything to classes isn't the purpose of TS, there's no use for classes here. JS code is supposed to be augmented with types and probably be enhanced with $inject annotation.

angular.module('app').config(decorateExceptionHandler);

decorateExceptionHandler.$inject = ['$provide'];

export function decorateExceptionHandler($provide: ng.auto.IProviderService) {
    $provide.decorator('$exceptionHandler', dispatchErrorEmail);
}

dispatchErrorEmail.$inject = ['$delegate', '$injector'];

export function dispatchErrorEmail(
    $delegate: ng.IExceptionHandlerService,
    $injector: ng.auto.IInjectorService
): (exception: any, cause: any) => void { ... }

config expects a regular function, not a constructor. And the reason why the original TS code fails is that ExceptionHandler isn't called with new , thus this is not an object, and this.dispatchErrorEmail is not a function.

Here's another way to do it using TypeScript namespaces . It feels a little cleaner to me, keeping the exception extension functions isolated, and is a nice conceptually coming from something like C#.

exception.module.ts

import * as angular from 'angular';
import { LoggerModule } from '../logging/logger.module';
import { ExceptionExtension } from './exception.handler';

export const ExceptionModule = angular.module('app.common.exception', [LoggerModule])
    .config(ExceptionExtension.Configure)
    .name;

exception.handler.ts

import { ILoggerService } from '../logging/logger.service';

export namespace ExceptionExtension {
    export const ExtendExceptionHandler = ($delegate: ng.IExceptionHandlerService, logger: ILoggerService) => {
        return function (exception: Error, cause?: string): void {
            $delegate(exception, cause);
            logger.error(exception.message, "Uncaught Exception", cause ? cause : "");
        }
    };
    ExtendExceptionHandler.$inject = ['$delegate', 'ILoggerService'];

    export const Configure = ($provide: ng.auto.IProvideService) => {
        $provide.decorator('$exceptionHandler', ExtendExceptionHandler);
    };
    Configure.$inject = ['$provide'];
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM