'Is there any way to modify the MatDialog overlay order?

I want to implement basic dialog handling in my application, with 2 functions:

  1. To close all open dialogs: Easy with MatDialog.closeAll();
  2. Enable the user to switch between open dialogs!

This is a problem for me, because it seems like there is no function to make a dialog the first one in order.

I can query all the open dialogs with MatDialog.openDialogs, and I can display the name of them for the user to chose from. After that i tried to add a panel class to the clicked one which modifies the z-index, but nothing happened.

What could be the solution?

My code:

HTML template for the menu, to handle dialog actions:

<mat-menu #menu="matMenu">
  <button mat-menu-item (click)="closeAll()">
    <mat-icon>close_all</mat-icon>
    <span>Close all</span>
  </button>
  <mat-divider></mat-divider>
  <button
    (click)="focusDialog(dialog, $event)"
    *ngFor="let dialog of this.dialog.openDialogs"
    mat-menu-item
  >
    <mat-icon>preview</mat-icon>
    <span>{{ dialog.componentInstance.data.title }}</span>
  </button>
</mat-menu>

Button click event handler:

focusDialog(dialogRef: MatDialogRef<any>, e: any) {
    
    e.stopPropagation();
    this.dialog.openDialogs.forEach((dialog) => {
      dialog.removePanelClass('dialog-focus');
    });
    dialogRef.addPanelClass('dialog-focus');
  }

dialog-focus css class:

.dialog-focus .mat-dialog-container{
  z-index: 1000 !important;
}

EDIT:

I couldn't seem to achive anyhting with z-index, but i could set the display of the dialogs to 'block' or 'none' and this way, i could see the preferred dialog. The problem is: I can not interact with it! The overlay of the lastly opened dialog is still there. How could I make it disappear, and then use it again if the usert switches back to its related dialog?

focusDialog(dialogRef: MatDialogRef<any>, e: any) {
    e.stopPropagation();
    this.dialog.openDialogs.forEach((dialog) => {
      dialog.removePanelClass('dialog-focus');
      dialog.addPanelClass('dialog-background');
    });
    dialogRef.addPanelClass('dialog-focus');
    dialogRef.removePanelClass('dialog-background');
  }
.dialog-focus .mat-dialog-container {
  z-index: 1000 !important;
  display: block;
}

.dialog-background .mat-dialog-container {
  z-index: 1 !important;
  display: none;
}

I inspected the HTML Tree and seems like the opened dialogs and the overlays are siblings.



Solution 1:[1]

Based on Eliseo's answer I came up with another solution, which fits my application better:

I did't want the parent component to manage the overlay order change, because I think it's more universal, if I create a component which is responsible for all dialog actions, and accepts a component as input to load it's template inside it's self. This way I can open a dilaog easily anywhere in my app, and open dialogs inside dialogs so on.

Based on Eliseo's answer, no panel class modification needed. Reordering the cdk-global-overlay-wrapper elements is enough.

Stackblitz link for the solution:

https://stackblitz.com/edit/angular-9-material-starter-8uhrgj?file=src/app/viewer-modal/viewer-modal.component.ts

How the files in the original question changed:

HTML

<mat-menu #menu="matMenu">
  <button mat-menu-item (click)="closeAll()">
    <mat-icon>close_all</mat-icon>
    <span>Close all</span>
  </button>
  <mat-divider></mat-divider>
  <button
    (click)="focusDialog(dialog, $event)"
    *ngFor="let dialog of this.dialog.openDialogs"
    mat-menu-item
  >
    <mat-icon>preview</mat-icon>
    <span>{{ dialog.componentInstance.data.title }}</span>
  </button>
</mat-menu>
focusDialog(dialogRef: MatDialogRef<any>, e: any) {
    const cdkOverlayWrappers = document.getElementsByClassName(
      'cdk-global-overlay-wrapper'
    );

    for (let i = 0; i < cdkOverlayWrappers.length; i++) {
      const wrapper = cdkOverlayWrappers[i];

      if (wrapper.contains(dialogRef.componentInstance.el.nativeElement)) {
        const parent = wrapper.parentNode!;
        const last = parent.lastChild;
        if (last != wrapper)
          last!.parentNode!.insertBefore(wrapper, last!.nextSibling);
        break;
      }
    }
  }

Solution 2:[2]

A mat-dialog add an div with class 'cdk-global-overlay-wrapper' to a div with class 'cdk-overlay-container' at bottom of the .html

So you can change the position of the element. But you need make in "pure javascript".

We can change the position of the element creating the function in each component in a modal, but it's better that was the parent who makes the work (you can also delegate in a service (*))

So, we inject the elementRef in constructor of our components in modal and declare an output

  @Output() toTop:EventEmitter<any>=new EventEmitter<any>()
  constructor(public el: ElementRef){}

When we want in a mousedown we emit the event. In this stackbliz, that add a cdkdrag to the "header" of a component I choose put also in the "drag" the mouse down

<!--as I declared in constructor as public the "el" we can use in .html-->
<h1 (mousedown)="toTop.emit(el)" mat-dialog-title cdkDragHandle 
    >Hi {{data.name}}</h1>

Well In parent we need a function that make's to top our element

  toTop(wich:ElementRef) {
    const elements = document.getElementsByClassName(
      'cdk-global-overlay-wrapper'
    );

    for (let i = 0; i < elements.length; ++i) {
      const el = elements[i];
      if (el.contains(wich.nativeElement)) {
        const parent = el.parentNode;
        const last = parent.lastChild;
        if (last != el){
          last.parentNode.insertBefore(el, last.nextSibling);
        }
        break;
      }
    }
  }

And, based in this another SO to comunicate a mat-dialog with the parent we makes, e.g.

const dialogRef = this.dialog.open(DialogOverviewExampleDialog, {
  width: '250px',
  hasBackdrop: false,
  data: {name: this.name, animal: this.animal}
});
const sub = dialogRef.componentInstance.toTop.subscribe((el) => {
  this.toTop(el)
});

(*) Using a service simply declare your service with the function

toTop(wich:ElementRef) {
  ....
}

Inject the service in your component that was in modal

   constructor(public modalService:ModalService,
               public el:ElementRef){}

And use

<h1 (mousedown)="modalService.toTop(el)" mat-dialog-title cdkDragHandle 
        >Hi {{data.name}}</h1>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 eogabor
Solution 2