2017-10-18 108 views
2

我試圖爲我的Angular4應用程序實現一般的ErrorHandler,目的是顯示一個對話框,其中包含有關我收到的錯誤的一些信息。使用Material Design的Angular2應用程序的一般錯誤處理程序

這是錯誤處理程序:

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); 
    } 
} 

雖然這是對話框:

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(); 
     } 
    } 
} 

這是我得出了一個plunkr:現在https://plnkr.co/edit/dZ9yNf?p=preview

,問題是在我發佈錯誤之後,MatDialog I顯示器不顯示消息並且不關閉。它正確地執行回調函數,但它保持在那裏。

  • 我一直無法重現plunkr中的行爲。我不知道爲什麼,但我懷疑它與這個事實有關,plunkr將我的HttpErrorResponse對象包含在另一個錯誤中。儘管如此,在我的開發環境中,即使我評論考慮錯誤類型的所有代碼,也存在錯誤。
  • 我試着用MatSnackBar代替MatDialog,沒有運氣,仍然是同樣的問題,小吃店出現並且不想消失。
  • 對話框本身也用於其他情況,在正常關閉的情況下,不會給出任何問題。
  • 調試我發現問題是,對話框試圖關閉自己,但從未發佈MatDialogContainer上的狀態更改事件,因爲執行後我看到狀態正在改變,但未調用回調。

我在做什麼錯?爲什麼這個奇怪的行爲你有什麼建議嗎?

P.S.評論的東西是我將需要的東西,但我已經能夠取出來簡化處理程序而不解決問題。它在那裏,不會被註釋,但它不是問題。

編輯︰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} 
    ) 
    } 

在這種情況下,​​順利,而throwError掛在那兒。如果有人知道解決方案,請幫忙。

回答

1

好像我在解決它之後沒有回答自己。原來這是一個角度爲here the issue I opened的錯誤,但不是你可能會想到的錯誤。實際上,通緝行爲是「非關閉」行爲,實際上UI並未更新。

原因是錯誤發生後,錯誤處理程序在NgZone之外執行,這意味着角度不會知道您所做的UI更改。 (我不得不穀歌zone.js瞭解所有這一切。)

解決方案是再次注入NgZone並在區域內手動運行東西。它可以工作,但由於區域內的錯誤被髮送回處理程序,因此可能會導致無限循環(錯誤,操作顯示錯誤會導致新錯誤,新錯誤會觸發相同的操作,從而再次拋出錯誤等等) ...)。我所做的是避免這樣做,即讓警衛不要向用戶界面顯示一個以上的錯誤,這樣除非用戶告訴我它沒問題,否則任何後續錯誤都會發送給控制檯。這裏是工作代碼:

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); 

    } 

} 

我注入NgZone的處理程序,並用它來顯示對話框,和我有詮布爾值,應避免無限遞歸。