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:
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
});
}
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.