[Angular Material] Dynamic Dialog Component

You know the struggle with angular dialog components when you create mainstream business software. Sometimes I feel like I'm just doing the same for each page again and again. Place a header, some type of grid and finally add all dialogs to it - like "Do you really want to delete it?".

But somehow every dialog is slightly different, so you can't reuse the whole dialog for each grid. Recently I stumbled over 10 of these kinds of components which are all the same but not really. The next thought was - "If we had to change anything around the main content of the dialog we have to change it 10 times".

Could we do better? I think so!
If you are just interested in the source code: Stackblitz

The Only Component We Need


Let us create a new component for our new all-in-one-dialog. Navigate to your preferred location and let the angular CLI work:

ng g c dynamic-dialog


Header over to the TS-File and let us prepare everything for being a dynamic component. For this, you need to inject the MAT_DIALOG_DATA injectionToken to share data with your dialog. Your constructor should look like the following:

constructor(@Inject(MAT_DIALOG_DATA) public data: IDynamicDialogConfig)


For typesafe data transfer between your component I created an interface "IDynamicDialogConfig" which is defined like this:

export interface IDynamicDialogConfig {
  title?: string;
  acceptButtonTitle?: string;
  declineButtonTitle?: string;
  dialogContent: TemplateRef<any>;
}

Properties:
- Title: Defines the Dialogs Title
- TextAcceptButtonTitle: Right-sided Button Text (positive feedback)
- DeclineButtonTitle: Left-sided Button Text (negative feedback)
- DialogContent: The dynamic content we want to display (more later)


Open up the HTML-File and put in your base dialog styling. I go with the angular material default and added the properties above to make everything dynamic:

<h2 mat-dialog-title>{{ data.title }}</h2>
<mat-dialog-content>
  <ng-container [ngTemplateOutlet]="data.dialogContent"> </ng-container>
</mat-dialog-content>
<mat-dialog-actions>
  <button *ngIf="data.declineButtonTitle" mat-button mat-dialog-close>
    {{ data.declineButtonTitle }}
  </button>
  <button mat-button [mat-dialog-close]="true" cdkFocusInitial>
    {{ data.acceptButtonTitle }}
  </button>
</mat-dialog-actions>

That's all for our dynamic dialog component - pretty basic!

Create and showing the dynamic templates


Now navigate to the component where you need to open the dialog and open the HTML-File to create the dynamic templates. You can place them anywhere you want. I normally put them at the bottom. (they don't get rendered)

<!-- Plain Text Example -->
<ng-template #yesNoDialogTemplate>
  <span>Do you really want to delete it?</span>
</ng-template>

<!-- Data Example -->
<ng-template #dataDialogTemplate>
  <span>Count is: {{ count }}</span>
</ng-template>


When you are finished with your templates we have to load them into a TemplateRef variable. We will use ViewChild for this task - head over to your components TS-File:  

@ViewChild('yesNoDialogTemplate') yesNoTemplate: TemplateRef<any> | undefined;  @ViewChild('dataDialogTemplate') dataTemplate: TemplateRef<any> | undefined;

Now all we have to do is to open up our dialogs! For testing purposes I created three buttons, a count variable and three functions:

HTML:

<div style="display: flex; flex-direction: column">
  <button (click)="openYesNoDialog()">Open YesNo Dialog</button>
  <br />
  <button (click)="openDataDialog()">Open Data Dialog</button>
  <button (click)="increaseCount()">Increase Count</button>
</div>

TS:

count: number = 0;

@ViewChild('yesNoDialogTemplate') yesNoTemplate: TemplateRef<any> | undefined;
@ViewChild('dataDialogTemplate') dataTemplate: TemplateRef<any> | undefined;

constructor(public dialog: MatDialog) {}

openYesNoDialog() {
  const dialogRef = this.dialog.open(DynamicDialogComponent, {
    width: '250px',
    data: <IDynamicDialogConfig>{
      title: 'Really?',
      dialogContent: this.yesNoTemplate,
      acceptButtonTitle: 'Delete it!',
      declineButtonTitle: 'No stop!',
    },
  });

  dialogRef.afterClosed().subscribe((result) => {
    if (!result) return;
    // delete it
  });
}

openDataDialog() {
  const dialogRef = this.dialog.open(DynamicDialogComponent, {
    width: '250px',
    data: <IDynamicDialogConfig>{
      title: 'Show the count!',
      dialogContent: this.dataTemplate,
      acceptButtonTitle: 'Ok',
      declineButtonTitle: '',
    },
  });
}

increaseCount() {
  this.count++;
}

As you see in the code above you can give the TemplateRef to your dialog and show it there. This even works with data! Just hit the increaseCount Button and open up the dialog. You will see the updated count in the dialog - awesome!

Running in trouble? Check the source code at Stackblitz

If you have further questions about this topic or need help with a problem in general, please write a comment or simply contact me at yesreply@georghoeller.dev :)