Search code examples
angularangular-materialangular-providersangular-errorhandlermaterial-dialog

Dialog opened by custom ErrorHandler provider in response to async event does not close


I have a general Alert Dialog component that I use for various purposes throughout an Angular application (simplified)...

@Component({
  selector: 'app-alert-dialog',
  template: `<mat-dialog-content>{{message}}</mat-dialog-content>
<mat-dialog-actions align="end">
  <button mat-raised-button mat-dialog-close>{{buttonText}}</button>
</mat-dialog-actions>`
})
export class AlertDialog {
  message: string = 'An unspecified error has occurred';
  buttonText = 'Cancel';

  constructor(
    @Inject(MAT_DIALOG_DATA)
    private data: {
      message: string;
      buttonText: string;
    },
    private dialogRef: MatDialogRef<AlertDialog>
  ) {
    if (data?.message) this.message = data.message;
    if (data?.buttonText) this.buttonText = data.buttonText;
  }
}

The dialog windows specify default options in the app.module providers:

@NgModule({
  ...
  providers: [
    { provide: ErrorHandler, useClass: ErrorHandlerService },
    // default options for dialogs
    {
      provide: MAT_DIALOG_DEFAULT_OPTIONS,
      useValue: {
        disableClose: true,
        hasBackdrop: true
      }
    }
  ]
})

I also implement a custom ErrorHandler provider that displays relevant errors through the Alert Dialog.

@Injectable({
  providedIn: 'root'
})
export class ErrorHandlerService extends ErrorHandler {
  constructor(private dialog: MatDialog) {
    super();
  }

  handleError(err: any): void {
    console.error(err);

    this.dialog.open(AlertDialog, {
      data: { message: err.message }
    });
  }
}

The errors displayed are frequently ones returned by the back-end server asynchronously.

In the case of an error being thrown asynchronously, the dialog does not close in response to the button (even with a (click)="closeDialog()" handler) unless you click the button and then also click elsewhere off the button.

I have a StackBlitz project that demonstrates the issue here: https://stackblitz.com/edit/angular-ivy-b3fqjj

Is there a way to close the dialog opened asynchronously with a single button click?


Solution

  • Use ngZone. Improved code:

      constructor(private dialog: MatDialog, private ngZone: NgZone) {
        super();
      }
    
      handleError(err: any): void {
        console.error(err);
        this.ngZone.run(() => {
          this.dialog.open(AlertDialog, {
            data: { icon: 'Error', message: err.message, buttonText: 'Uh oh!' }
          });
        });
      }
    

    For more information see answer.