How to create a reusable dialog/modal in Angular



In this article, we will develop a solution to make an angular material dialog or modal component reusable which can be invoked by any components within the project.

Features of reusable material dialog that we are developing

  1. The dialog can be invoked with any label for the affirmative and negation actions
  2. Once the Submit button is clicked any callback function can be triggered
  3. An animation will be displayed when the async process is ongoing and also submit or cancel buttons will be disabled

How to setup Angular material

You can add the angular material to any existing angular project using the cli schematics with ng add @angular/material and then you need to import all the necessary component from the angular material into app.module.ts and the file looks as below,

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { DialogComponent } from './components/shared/dialog/dialog.component';
import { FormsModule } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatDialogModule } from '@angular/material/dialog';
import { MatButtonModule } from '@angular/material/button';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';

@NgModule({
  declarations: [AppComponent, DialogComponent],
  imports: [
    BrowserModule,
    FormsModule,
    BrowserAnimationsModule,
    MatFormFieldModule,
    MatDialogModule,
    MatButtonModule,
    MatProgressBarModule,
    MatProgressSpinnerModule,
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

Folder scaffolding

Since we are now developing something reusable and it's common practice that is placed in the shared folder, hence our dialog component is generated inside src/app/components/shared and you can generate the component directly from the cli using the command ng g c components/shared and this will generate the components in the specified folder.

Design of reusable dialog component

Now we will go through in detail about developing the reusable modal component

Inputs for dialog component

We are going to take dialog header, dialog content, label for Affirmative action such as Submit, Confirm and negative action such as Cancel and any callback method that should be triggered once the user clicks on the Submit button.

Interface for dialog

Based on the above information, our dialog needs the below interface to manage the data

export interface DialogInterface {
  cancelButtonLabel: string;
  confirmButtonLabel: string;
  dialogHeader: string;
  dialogContent: string;
  callbackMethod: () => void;
}

Design of dialog

We can design the dialog in such a way that the header, content, and to access the data in the dialog component, we have to use the MAT_DIALOG_DATA injection token

<div>
  <mat-progress-bar
    mode="indeterminate"
    *ngIf="stateService.isAsyncOperationRunning$ | async"
    style="top: -25px"
  ></mat-progress-bar>
  <h1 mat-dialog-title>{{ dialogData.dialogHeader }}</h1>
  <div mat-dialog-content>
    <p>{{ dialogData.dialogContent }}</p>
  </div>
  <div mat-dialog-actions>
    <button
      mat-button
      (click)="handleDialogSubmit()"
      mat-raised-button
      color="primary"
      [disabled]="stateService.isAsyncOperationRunning$ | async"
    >
      {{ dialogData.confirmButtonLabel }}
    </button>
    <button
      mat-button
      cdkFocusInitial
      (click)="closeDialog()"
      [disabled]="stateService.isAsyncOperationRunning$ | async"
      mat-raised-button
      color="accent"
    >
      {{ dialogData.cancelButtonLabel }}
    </button>
  </div>
</div>

In the component, we will be invoking the method handleDiaglogSubmit whenever a user clicks on the submit button and we are closing the dialog as soon user click on negation operation such as Cancel for the example, as you can see below, first we are emitting the value of isAsyncOperationRunning as true so that we can use it to display an animation on the dialog when the operation is running maybe by using a spinner or a progress bar and used the triggered callback method inside a setTimeout method so we can mock the async operation.

  handleDialogSubmit() {
    this.stateService.isAsyncOperationRunning$.next(true);
    setTimeout(() => {
      this.dialogData.callbackMethod();
      this.stateService.isAsyncOperationRunning$.next(false);
    }, 500);
  }

Example of reusing the dialog

In the below code, you can notice that we are able to trigger two different dialogs using the same dialog component and also callback method for each method are different, when you on the submit of 1st dialog we are going to invoke the performDialogSubmitMethodOne method and on click of 2nd dialog, we are going to invoke performDialogSubmitMethodTwo

import { Component, Input } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { DialogComponent } from './components/shared/dialog/dialog.component';
import { DialogInterface } from './interfaces/dialog.interface';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent {
  public dialogSubmissionMessage: string = '';
  constructor(public dialog: MatDialog) {}

  /**
   * This method invokes the first dialog
   */
  openDialogOne() {
    const dialogInterface: DialogInterface = {
      dialogHeader: 'I am created by Reusable dialog',
      dialogContent: 'I am first dialog',
      cancelButtonLabel: 'Cancel',
      confirmButtonLabel: 'Submit',
      callbackMethod: () => {
        this.performDialogSubmitMethodOne();
      },
    };
    this.dialog.open(DialogComponent, {
      width: '300px',
      data: dialogInterface,
    });
  }

  /**
   * This method invokes the second dialog
   */
  openDialogTwo() {
    const dialogInterface: DialogInterface = {
      dialogHeader: 'I am created by Reusable dialog',
      dialogContent: 'I am second dialog',
      cancelButtonLabel: 'Cancel',
      confirmButtonLabel: 'Submit',
      callbackMethod: () => {
        this.performDialogSubmitMethodTwo();
      },
    };
    this.dialog.open(DialogComponent, {
      width: '300px',
      data: dialogInterface,
    });
  }

  performDialogSubmitMethodOne() {
    this.dialogSubmissionMessage = 'The dialog submitted from the Dialog ONE';
  }

  performDialogSubmitMethodTwo() {
    this.dialogSubmissionMessage = 'The dialog submitted from the Dialog TWO';
  }
}

Demo and Git Repo

Demo of the reusable dialogs can be seen below



Git Repohttps://github.com/allabouttech0803/angular-reusable-dialog

0
0