使用 Material Design 的 Angular2 应用程序的一般错误处理程序

bra*_*o23 2 error-handling dialog typescript material-design angular

我试图为我的 Angular4 应用程序实现一个通用的 ErrorHandler,目的是显示一个对话框,其中包含有关我收到的错误的一些信息。

这是错误处理程序:

import { ErrorHandler, Injectable, Injector } from '@angular/core'
// import { Router } from '@angular/router'
import { MatDialog } from '@angular/material'

import { HttpErrorResponse } from '@angular/common/http'

import { MessageDialog } from './message.dialog'
// import { LoginService } from '../login/main.service'


@Injectable()
export class DialogErrorHandler implements ErrorHandler {

    constructor(private injector: Injector) {
    }

    handleError(error: any): void {
        let localError = error;
        let finalMessage: string = "Errore sconosciuto";
        let finalCallback: () => void = () => { console.log("default callback")};

        let dialog: MatDialog = this.injector.get(MatDialog);
        // let login: LoginService = this.injector.get(LoginService);
        // let router: Router = this.injector.get(Router);
        if( localError instanceof HttpErrorResponse && localError.error instanceof Error){
            localError = localError.error
        }
        if (localError instanceof HttpErrorResponse) {
            console.log("http error");
            let errorDesc = localError.status + " " + localError.statusText + ": ";
            switch (localError.status) {
                case 403:
                    finalMessage = "La sessione è scaduta, ripeti il login.";
                    // finalCallback = () => { login.logout(); router.navigate['/login']; };
                    break;
                case 500:
                    finalMessage = "Errore sul server - " + errorDesc + localError.error;
                    break;
                default:
                    finalMessage = errorDesc;
            }
        } else {
            finalMessage = localError.toString();
            finalCallback = () => { location.reload() };
        }
        dialog.open(MessageDialog, {
            data: {
                message: finalMessage,
                callback: finalCallback
            }
        })
        console.log(error);
    }
}
Run Code Online (Sandbox Code Playgroud)

虽然这是对话框:

import { Component, Inject } from '@angular/core'

import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'

@Component({
    selector: 'message-dialog',
    template: `
        <p mat-dialog-title color="primary" class="centered">Attenzione</p>
        <div mat-dialog-content>{{message}}</div>
        <div mat-dialog-actions style="display: flex; justify-content: center">
            <button mat-button mat-dialog-close (click)="handleClick()">
                OK
            </button>
        </div>
    `,
    styleUrls: ['../common/style.css']
})
export class MessageDialog {

    message: string = "cose";
    callback: () => void;

    constructor( @Inject(MAT_DIALOG_DATA) private data,
        private diagref: MatDialogRef<MessageDialog>) {
        if (data) {
            this.message = data.message;
            this.callback = data.callback;
        }
    }

    handleClick() {
        this.diagref.close();
        if (this.callback) {
            this.callback();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是一个 plunkr 与我来过:https ://plnkr.co/edit/dZ9yNf?p=preview

现在,问题是在我启动错误后,我显示的 MatDialog 不显示消息并且不关闭。它正确地执行回调函数,但它停留在那里。

  • 我无法在 plunkr 中重现该行为。我不知道为什么,但我怀疑这与 plunkr 将我的 HttpErrorResponse 对象包装在另一个错误中的事实有关。尽管如此,在我的开发环境中,即使我注释了所有考虑错误类型的代码,错误仍然存​​在。
  • 我试过用 MatSnackBar 替换 MatDialog,没有运气,仍然是同样的问题,snackbar 出现并且不想消失。
  • 对话框本身也用于其他情况,在这些情况下它正常关闭,不会出现任何问题。
  • 调试我发现问题在于对话框以某种方式尝试关闭自己,但从未发出 MatDialogContainer 上的状态更改事件,因为在执行之后我看到状态更改但没有调用回调。

我究竟做错了什么?为什么会有这种奇怪的行为?你有什么建议吗?

PS评论的东西是我需要的东西,但我已经能够在不解决问题的情况下简化处理程序。它在那里,将不被注释,但这不是问题。

编辑:9 天后,风滚草徽章和其他东西,我已经能够重现错误。同样的plunkr,不同之处在于错误的来源。特别是:

handleClick(){
    Observable.throw(new Error("local error")).subscribe(
      () => {},
      (err) => {throw err)
    )
  }

  throwError(){
    this.http.get("http://www.google.com/thisshouldnotexist").subscribe(
      () => {},
      (err) => {throw err}
    )
  }
Run Code Online (Sandbox Code Playgroud)

在这种情况下,handleClick进展顺利,而throwError挂在那里。如果有人知道解决方案,请帮忙。

bra*_*o23 5

解决之后好像我还没有回答自己。原来这是我打开的问题的角度错误,而不是您可能认为的错误。实际上想要的行为是“非关闭”行为,这实际上是 UI 没有更新。

原因是发生错误后,错误处理程序在 NgZone 之外执行,这意味着 angular 不会知道您所做的 UI 更改。(我不得不谷歌 zone.js 来了解这一切。)

解决方案是再次注入 NgZone 并在区域内手动运行东西。它可以工作,但是由于区域内的错误会被发送回处理程序,因此可能会导致无限循环(错误,显示错误的操作会导致新错误,新错误会触发再次引发错误的相同操作,依此类推……)。为了避免这种情况,我所做的是设置一个保护器,不要向 UI 显示多个错误,这样除非用户告诉我可以,否则任何后续错误都会发送到控制台。这是工作代码:

import { ErrorHandler, Injectable, Injector, NgZone } from '@angular/core'

import { Router } from '@angular/router'

import { MatDialog } from '@angular/material'


import { HttpErrorResponse } from '@angular/common/http'


import { MessageDialog } from '../dialogs/message.dialog'

import { LoginService } from '../login/main.service'



@Injectable()

export class DialogErrorHandler extends ErrorHandler {


    private elaborating: boolean = false;


    constructor(private injector: Injector, private ngzone: NgZone) {

        super();

    }


    handleError(error: any): void {

        if (!this.elaborating) {

            this.elaborating = true;

            let localError = error;

            let finalMessage: string = "Errore sconosciuto";

            let finalCallback: () => void = () => { console.log("default callback") };


            let dialog: MatDialog = this.injector.get(MatDialog);

            let login: LoginService = this.injector.get(LoginService);

            let router: Router = this.injector.get(Router);

            // nessun dialog per TypeError, evita problemi con MatSelect

if (localError instanceof TypeError) {

                this.elaborating = false;

            } else {

                if (localError instanceof HttpErrorResponse && localError.error instanceof Error) {

                    localError = localError.error

                }

                if (localError instanceof HttpErrorResponse) {

                    let errorDesc = 'Request to ' + localError.url + "\n" + localError.status +

                        " " + localError.statusText + ": " + localError.error;

                    switch (localError.status) {

                        case 403:

                            finalMessage = "La sessione è scaduta, ripeti il login.";

                            finalCallback = () => { login.logout(); router.navigate['/login']; };

                            break;

                        case 500:

                            finalMessage = "Errore sul server - " + errorDesc;

                            break;

                        default:

                            finalMessage = errorDesc;

                    }

                } else {

                    finalMessage = localError.message;

                }

                this.ngzone.run(() => {

                    dialog.open(MessageDialog, {

                        data: {

                            message: finalMessage,

                            callback: () => { finalCallback(); this.elaborating = false; }

                        }

                    })

                })

            }

        }

        super.handleError(error);

    }

}
Run Code Online (Sandbox Code Playgroud)

我在处理程序中注入 NgZone 并使用它来显示对话框,并且我有一个应该避免无限递归的详细布尔值。