Confirm Dialog Decorator 🙌 with Angular

Make Using Confirmation Dialogs Easier with a Decorator

Zied ZAYANI
ITNEXT

--

Image by Pexels from Pixabay

Sometimes in our application, there may be a critical action ⚠️💥 that requires confirmation from the user before it can be executed.

To ask the user for confirmation or cancellation, a confirm dialog is used.

However, to prevent the need for redundant code to display this dialog, this article will demonstrate how to develop a decorator that can perform this task whenever necessary.

A decorator is a simple function that can modify the behavior of another function or object by wrapping it and adding extra functionality without directly modifying the original source code.

The application in this article is based on standalone components. To learn how to build applications using standalone components, check out my article

But of course, you can use a classic application with modules 🙂

Before we begin implementing our decorator, I have prepared a simple example application that displays a list of users and includes a delete button for each row.

To achieve this I used Angular Material Table to show a static list of users

and in the template:

And here is an example of what our implementation does:

As you can see, adding a confirmation dialog for critical actions is crucial to prevent accidental deletion 😱 or unintended consequences. It provides an extra layer of caution before executing irreversible actions.

Now, instead of using a traditional approach, we will avoid code duplication and simplify the use of this action’s code by making it reusable throughout the application 🌱 in a straightforward manner.

By using decorator, we can efficiently implement this reusable functionality for critical actions without duplicating code and simplify its usage across the application.

In this article, our objective is to demonstrate how we can use a decorator to show a confirmation dialog as follows:

And with just one line, we can display a confirmation dialog.

Let’s get started 🚀🚀🚀

Create a simple Confirm Dialog with Angular Material

In this section, we will cover the following topics:

  • Preparing the data to be displayed in the confirmation dialog
  • Creating a service that can open a dialog with a specific component
  • Developing the component that will be displayed in the dialog

Preparing the data to be displayed in the confirmation dialog

We will start by creating an interface that represents the data needed for our Confirm Dialog, including the title and message.

ng g interface confirm-dialog-data
export interface ConfirmDialogData {
title: string;
message : string;
}

Creating a Service for Dialog Management

To open a dialog in Angular Material, we need to create a service that includes a function responsible for opening the dialog. This function should accept a component as an argument and use it to open the dialog, along with any data that the component requires.

ng g service service/dialog

In this service, we inject the MatDialog service with the function inject(You can use the constructor to inject MatDialog). The MatDialog service provides the functionality for opening dialogs in Angular Material.

The openDialog function takes two parameters, data and component. The data parameter represents any data that the component needs, while the component parameter represents the type of component used in the dialog.

The function openDialog calls the open method of the MatDialog. A dialog is opened by calling this method with a component to be loaded and an optional object(data in ou case).

The disableClose property is set to “true”, which means that the user cannot close the dialog by clicking outside of it.

Creating the Component for Our Confirm Dialog

Now we will create our component, ConfirmDialogComponent, which will contain the content of the confirmation dialog for the user’s action.

ng g c confirm-dialog --standalone

To retrieve data sent by the service in the component, we can use @Inject(MAT_DIALOG_DATA) to inject the data that was passed to the dialog. For our ComponentConfirmDialogComponent, the injected data parameter will contain the title and message properties that were passed when the dialog was opened.

Now that we have retrieved the data, we can use it in the component template.

Confirmation Dialog With 🔮Decorator 🙌

We’re all set to begin the implementation process 📝✅. it’s time now to create our decorator 🛠️👨‍💻.

In this section, we will implement a decorator that use the Dialog Service to display the confirmation dialog.

To create our decorator, we first need to create a TypeScript file called confirm-dialog.decorator.ts.

Next, create a function called needConfirmation inside the confirm-dialog.decorator.ts file.

This function will take a confirmDialogData parameter, which is an object containing the title and message to be displayed in the ConfirmDialogComponent.

import { ConfirmDialogData } from "./confirm-dialog-data";

const defaultConfirmData = {
title: "Confirmation",
message: "Are you sure you want to perform this action?"
}


export function needConfirmation ( confirmData : ConfirmDialogData = defaultConfirmData) {

return function (target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;

return descriptor;
};


}

This code represents the body of the needConfirmation decorator function. Here's a breakdown of what's happening:

  • The function takes in three parameters: target, propertyKey, and descriptor. These parameters are automatically passed in by TypeScript when the decorator is applied to a class method.
  • The originalMethod variable is assigned the value of the original method that the decorator is applied to. This is done so that we can call the original method after the user confirms the action in the ConfirmDialog.
  • The function returns the descriptor object as is. The descriptor object contains information about the method that the decorator is applied to, including its value, configurable, enumerable, and writable properties.

This decorator function initially returns the descriptor object unchanged. However, we will modify its behavior by adding the confirmation dialog before the execution of the method. The method will only be executed if the user clicks the confirm button. If the user decides to cancel the action, the original method will not be executed.

Now we need to inject DialogService into our decorator, but the decorator is running outside the Angular context 😱, so we can’t use dependency injection.

What can we do? 🤔

💡To work around this, we have to modify our dialog service

import { ComponentType } from '@angular/cdk/portal';
import { inject, Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Observable } from 'rxjs';

@Injectable({
providedIn: 'root'
})
export class DialogService {

matDialog = inject(MatDialog);

private static instance : DialogService | null= null;
constructor(private dialog: MatDialog) {
DialogService.instance = this;
}
public static getInstance(){
return DialogService.instance;
}


openDialog<T>(data : any,component: ComponentType<T>) : Observable<boolean>{

return this.matDialog.open(component,{
data : data,
disableClose: true,
}).afterClosed();

}
}

This code provides a way to use the DialogService outside of an Angular context by creating a static instance of the service and initializing it with the first creation of the service. This instance can then be accessed using a static method, allowing us to call the service’s methods without the need for dependency injection.

Now we can get the instantiated service outside the Angular context by using:

DialogService.getInstance();

🚨 But we need to inject this service to be sure that the instance exists when we need it.

🎯 Now we have to ensure that our dialog service is injected before using it, to initialize the instance attribute, there is many solutions to do this, in our example, we will use ENVIRONMENT_INITIALIZER

ENVIRONMENT_INITIALIZER: A multi-provider token for initialization functions that will run upon construction of an environment injector.

  • Modify main.ts

We can now modify the needConfirmation decorator to use the DialogService and open the dialog before the execution of the original method.

🎉 Our decorator is ready to use! 🥳

Now, whenever we need confirmation before action, we can simply add our decorator before the function without the need to inject the service or duplicate code to open the confirmation dialog. We just need to use the decorator as @needConfirmation() before the function.

If you want to stay up-to-date with the latest blog posts and interesting frontend and backend topics, be sure to follow me on Twitter for notifications.

Alternatively, if we need to customize the default confirmation message, we can simply pass a new message as a parameter to the @needConfirmation() decorator like :

@needConfirmation({title:"",message:""})

Now it’s time to test our application :

Our decorator is strictly typed, meaning that we cannot call the decorator with incorrect parameters. If we do, the compiler will generate an error to inform us.

That’s IT! 🎉 We’ve learned how to simplify confirmation dialogs using a decorator.

Don’t forget to check out the example in my GitHub Repository and support this article with your 👏 to help share it with more people. And be sure to follow me for more exciting articles in the future! 🔥

--

--