Managing Multiple Dialogs in Angular Material Projects
Dinesh Haribabu
Posted on October 16, 2024
In Angular Material applications, the usage of dialogs is pretty common and becomes challenging when you need to manage multiple dialogs simultaneously. By default in Angular Material, the most recently opened dialog always stays on top, making it difficult to interact with others that are opened earlier. In this post, I’ll walk you through a couple of practical solutions, starting with a z-index approach and then delving into a better alternative for most cases.
The z-index fix: First thought, not always the best
For stacking issues of elements, the go-to approach would be to use the z-index
property to control which element appears on top. In case of multiple dialogs, by bumping the z-index
of the dialog that receives the click, we can allow users to interact with it by bringing it to the front.
Here’s the code for that solution:
public bringToFront(dialogId: string): void {
this.cdk.getContainerElement().childNodes.forEach((x: any) => {
if (x.innerHTML.indexOf('id="' + dialogId + '"') <= 0) {
x.style["z-index"] = 1000;
} else {
x.style["z-index"] = 1001;
}
});
}
And in the template:
<div (mousedown)="bringToFront(data.id)">
{{data.dialogContent}}
</div>
Problem: Overlay components break the z-index fix
While this works for basic dialogs, the problem appears when a dialog includes components that use overlays—for example, dropdowns, tooltips, or menus. These elements are wrapped inside an overlay container and all of them have a z-index
of 1000 by design.
When you try to bring a dialog to the front using a z-index
of 1001, the overlay components inside that dialog still have a z-index
of 1000. This makes the overlay elements inaccessible because they are displayed under the dialog. So while the solution works for plain dialogs, it falls short in these more complex cases.
Here’s how the overlay issue looks in action:
A better solution: Reordering DOM elements
Since manipulating the z-index
alone isn’t a comprehensive solution, we can turn to another approach: reordering the dialogs in the DOM hierarchy. In the DOM, elements with the same z-index
are displayed based on their order in the hierarchy—the element closer to the bottom of the DOM tree appears on top in the UI.
By appending the dialog element to the end of its container, we can ensure it gets displayed on top:
public bringToFront(dialogId: string): void {
const focusedDialog = Array.from(this.cdk.getContainerElement().childNodes).find(
(el: HTMLElement) => el.querySelector(`#${dialogId}`)
);
if (focusedDialog) {
// appendChild moves the element to the end of the container
this.cdk.getContainerElement().appendChild(focusedDialog);
}
}
In this solution, when a dialog is clicked, it is effectively “moved” to the top of the stack by being appended as the last child in the container. This ensures that overlay components such as dropdowns will now appear correctly on top of the dialog.
The Catch: Closing the dialog with ESC key
While this approach works for most use cases, it’s not 100% perfect. The current dialog reference maintained by Angular Material remains the last opened dialog. So, if you try to close the dialog using the ESC key, Angular Material will close the last opened dialog, not the one brought to the front.
Workaround: Custom ESC Key Listener
To resolve this, we need to manage the currently active dialog ourselves and override Angular Material’s ESC key listener. Here’s how to set up a custom ESC listener that closes the right dialog:
- Keep track of the currently focused dialog in your code.
- Override the ESC key functionality to close the dialog you’re managing.
- Similarly, override other actions (like backdrops) to handle the right dialog.
It’s not ideal to override default listeners, but this approach will give you more control over the dialog behavior. I wish there was a way to update the current dialog reference exposed through an API so we don't have to override the listeners.
A more polished version using Services and Directives
For better code organization and reusability, I’ve structured the final solution using services and directives. This approach allows for cleaner code and easier integration into larger projects. You can check out the full implementation here:
Stackblitz Example - Managing Multiple Dialogs
With these solutions in hand, you’ll be able to manage multiple dialogs in Angular Material projects without the frustrations of stacking issues. While it’s not perfect, understanding how z-index and DOM elements reordering work will help you handle most cases, even when overlay components are involved.
Posted on October 16, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.