3

I have made an Angular 6 app where I have some MatDialog. This works perfectly in development mode.

Now I have to implement this into an existing website (that uses sitecore 9). This works great, except that the dialogs won't open correctly. I've found out that the dialogs are added to the body and not to my angular root DOM element.

<html>
  <head></head>
  <body>
    <app-library>...</app-library>

    <div class="cdk-overlay-container">...</div>
  </body>
</html>

Is there a way to add the dialog to the angular app element (in my case ) and not to the body element?

I've already tried to use the viewContainerRef option matDialog, but this doesn't seem to work:

export class AppComponent implements OnInit {
  constructor(..., public viewRef: ViewContainerRef) {
}

export class FilterToggleComponent implements OnInit {
  constructor(..., public dialog: MatDialog, private appRef: ApplicationRef) {}
  openFilterPanel() {
    const viewRef = (this.appRef.components[0].instance as AppComponent).viewRef;
    const dialogRef = this.dialog.open(FilterPanelComponent, {
      width: '100vw',
      height: '100%',
      panelClass: 'search-filter-panel-modal',
      viewContainerRef: viewRef
    });
  }
}
Greg A.
  • 75
  • 1
  • 8
  • I don't think you have any control over the overlay container. Please explain "won't open correctly" - that might be fixable. – G. Tranter Oct 11 '18 at 20:19
  • There's a scroll animation to the bottom of my page and it adds whitespace inside my html. I could fix it by making my MatDialog position fixed, but that's not the result I want (I want the dialog to be restricted to my angular app DOM element). If it's not possible, i'll have to make it position fixed I guess... – Greg A. Oct 12 '18 at 07:28

2 Answers2

8

According to Angular Material docs, viewContainerRef option of MatDialogConfig is:

Where the attached component should live in Angular's logical component tree. This affects what is available for injection and the change detection order for the component instantiated inside of the dialog.

This does not affect where the dialog content will be rendered.

When inspecting the DOM when a dialog is opened, we'll find the following component hierarchy:

  1. Body - <body>
  2. OverlayContainer - <div class="cdk-overlay-container"></div>
  3. Overlay - <div id="cdk-overlay-{id}" class="cdk-overlay-pane">
  4. ComponentPortal - <mat-dialog-container></mat-dialog-container>
  5. Component - our ComponentExampleDialog component, which is opened like: this.dialog.open(ComponentExampleDialog).

Now, what we really need to change is the position in the DOM of the OverlayContainer, which, according to docs, is:

the container element in which all individual overlay elements are rendered.

By default, the overlay container is appended directly to the document body.

Technically, it is possible to provide a custom overlay container, as described in documentation, but, unfortunately, it is a singleton and creates a potential problem:

MatDialog, MatSnackbar, MatTooltip, MatBottomSheet and all components that use the OverlayContainer will open within this custom container.

If this is not a problem, use the following code, which attaches the overlay container to the element with id="angular-app-root":

@NgModule({
  providers: [{provide: OverlayContainer, useClass: AppOverlayContainer}],
 // ...
})
export class MyModule { }

export class AppOverlayContainer extends OverlayContainer {

  protected _createContainer(): void {
    const container: HTMLDivElement = document.createElement('div');
    container.classList.add('app-overlay-container');

    const element: Element | null = document.querySelector('#angular-app-root');
    if (element !== null) {
      element.appendChild(container);
      this._containerElement = container;
    }
  }
}

Then, add the following css:

.app-overlay-container {
  position: absolute;
  z-index: 1000;
  pointer-events: none;
  top: 0;
  left: 0;
  height: 100%;
  width: 100%;
}

.app-overlay-container:empty {
  display: none;
}

Stackblitz demo: https://stackblitz.com/edit/angular-wksbmf

Also the following article might be useful, because it contains a similar implementation: https://blog.bbogdanov.net/post/angular-material-overlay-hack-overlay-container/

andreivictor
  • 7,628
  • 3
  • 48
  • 75
  • I found I needed `position: fixed`, not `position: absolute`. – Roobot May 12 '21 at 23:04
  • 1
    Also, if you need to do this in an Angular Elements web component, this may help: https://stackoverflow.com/a/67512186/11991371 – Roobot May 12 '21 at 23:30
0

@andreivictor's solution worked for me (with position: fixed), and made sure my backdrop and spinner only cover the Angular Elements web component.

I also have a mat-drawer in my app. Originally I was appending app-overlay-container to the component that was contained in mat-drawer-content. The mat-drawer, when it opens, contains a form that contains a select (drop-down) element. Whilst everything else worked, the select was no longer visible and hence was unusable. I discovered that mat-select-panel elements were appearing in the app-overlay-container, which was covered up by the very mat-drawer that had spawned the mat-select-panel.

The lesson learned is that app-overlay-container really must be added to the outermost element in an Angular Elements web component. I ended up adding an extra wrapper/container div in app.component.html purely to achieve this, which I was reluctant to do, but was the only way I could get it to work. The wrapper div needed to have width and height specified explicitly, and the element that was previously outermost needed both width: 100% and height: 100%. (But YMMV as some of these may be specific to my individual project.)

Martin Q
  • 1
  • 2