Managing Multiple Dialogs in Angular Material Projects

dinesh-se

Dinesh Haribabu

Posted on October 16, 2024

Managing Multiple Dialogs in Angular Material Projects

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.

z-index fix

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;
    }
  });
}
Enter fullscreen mode Exit fullscreen mode

And in the template:

<div (mousedown)="bringToFront(data.id)">
  {{data.dialogContent}}
</div>
Enter fullscreen mode Exit fullscreen mode

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:

z-index issue

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

Elements Reordering Fix

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:

  1. Keep track of the currently focused dialog in your code.
  2. Override the ESC key functionality to close the dialog you’re managing.
  3. 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.

💖 💪 🙅 🚩
dinesh-se
Dinesh Haribabu

Posted on October 16, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related