How to Manage Modal Windows in Angular Apps

Written by amirankou | Published 2024/02/26
Tech Story Tags: angular | modal-windows | primeng | dynamic-imports | optimization | bundle-reducing | angular-guide | app-development

TLDRvia the TL;DR App

Every application is built from the same type of components - buttons, inputs, dropdowns, etc., and modal windows are in this list. The reason is simple - modal windows allow us to communicate with a user and do not overwhelm a UI with additional information until it becomes needed. In this article, I will explore one of the approaches to managing modal windows in Angular apps.

We use the primeng library for modals in the article's examples. Your case can be different. The general idea will be the same despite the libraries, but API can be different; be aware of it.

Modal windows are just regular components - they have markup, style, and selector. But in contrast to regular components, they usually don't have to be shown on the page immediately. We need them only for the result of users' actions - confirm, discard, fill a form, etc. That's why we won't include them in our build - we will download them dynamically only if we require them. Let's see an example:

public openModal(): void {
    const modalComponent = async (): Promise<any> => import('./path-to-your-modal');
    this.modalService.open(modalComponent());
}

Here, we have the dynamic import approach. It means that our component will be loaded when we invoke the openModal method. It's quite useful because we don't load the modal immediately in our chunk - only when the user needs it. This approach can help you to slightly reduce the bundle size, especially if your modals are full of logic and components.

In general, this is the working solution. But we can go further. Let's make a service that will handle the dynamic imports and return an instance of the modal:

import { Injectable } from '@angular/core';
import { DialogService } from 'primeng/dynamicdialog';

@Injectable({ providedIn: 'root' })
export class CustomModalService {
    public modalWindowComponents: { [key: string]: () => Promise<any> } = {
        ConfirmModalComponent: () =>
            import('your-path/to-component').then((component) => component.ConfirmModalComponent),
    };

constructor(private modalService: DialogService) {}

    public async openDynamicDialogModal({
        componentName,
        data,
    }: {
        componentName: string;
        data?: { [key: string]: any };
    }): Promise<any> {
        const modalComponent = this.modalService.open(await this.modalWindowComponents[componentName](), { ...data });

        modalComponent.onClose.subscribe(() => {
            // some logic
        });
        return modalComponent;
    }
}

Here, we can see several important things:

  1. We use an object to store modal windows imports where the key is the component name, and the value is an import function that returns Promise;
  2. Our method is taking the params object with the component's name (to take the import function from the object) and some additional data, which contains whatever you want for a component or its appearing configuration. Also, we have a subscription for close events. It is more convenient to handle window closing in one place;
  3. We return a Promise with the imported component. It allows us to handle the logic for each modal.

Here is an example of the usage of this service:

public async handleOpeningModalWindow(): Promise<void> {
    const dialogRef = await this.primengModalService.openDynamicDialogModal({
        componentName: 'DeleteStreamModalComponent',
        data: {
           // your params
        },
    });

    dialogRef.onClose
        .pipe(
            // your pipes
        )
        .subscribe(() => {
            // your logic
        });
}

Conclusion

By adopting the approach outlined above, Angular developers can achieve a leaner, more manageable modal window system. This strategy not only reduces bundle size but also offers a unified method for modal control and customization. Embracing dynamic imports and service-based architecture empowers developers to optimize user experiences while maintaining code scalability and efficiency.


Written by amirankou | Passionate about Front-End development
Published by HackerNoon on 2024/02/26