Search code examples
angularangular-materialangular-cdk

Angular CDK - Injecting the Custom overlay container class into a component creates new instance of it


Requirement

Use a custom OverlayContainer class in order to open Angular Material Snackbars on a specific DOM element (not on body).

At this point, I know that:

  • the OverlayContainer class is a singleton
  • I want to display only the snackbars on a specific DOM element and let the Dialogs, Tooltips and the other Material elements use the default overlay container element that is appended to body.

The code I've tried

I provided the custom class in the providers array.

providers: [
  {provide: OverlayContainer, useClass: AppOverlayContainer}
]

And the class is:

export class AppOverlayContainer extends OverlayContainer {

  /**
   * Append the OverlayContainer to the custom element
   */
   public appendToCustomContainer(element: HTMLElement): void {


     // this._containerElement is 'cdk-overlay-container'
     element.appendChild(this._containerElement);
   }

   **
   * Remove the OverlayContainer from the custom element and append it to body
   */
   public removeFromCustomContainer(): void {
     this._document.body.appendChild(this._containerElement);
   }
}

In my component, before opening a snackbar, I will call:

AppOverlayContainer.appendToCustomContainer(HTMLElement)

And after the snackbar closes, I will call:

AppOverlayContainer.removeFromCustomContainer();

The issue:

When injecting the AppOverlayContainer into the component constructor, it will create a new instance of this class and also of the OverlayContainer:

constructor(
  private _snackBar: MatSnackBar,
  private _appOverlayContainer: AppOverlayContainer
) {}

It works only if I inject the OverlayContainer, then use it like:

constructor(
  private _snackBar: MatSnackBar,
  private _appOverlayContainer: OverlayContainer
) {}
(this._appOverlayContainer as AppOverlayContainer).appendToCustomContainer(appOverlayWrapperContainer);

(this._appOverlayContainer as AppOverlayContainer).removeFromCustomContainer();

Which seems kind of tricky.

I really don't understand what I am missing here.

Stackblitz

https://stackblitz.com/edit/angular-wksbmf-9xdxcv


Solution

  • You should use useExisting to avoid this problem having two instances of your services. The reason is some components are asking for OverlayContainer and some are asking for AppOverlayContainer and angular created instances for each. Updating the useClass to useExisting will help to let angular only use one instance:

    providers: [
      {provide: OverlayContainer, useExisting: AppOverlayContainer}
    ]
    

    You can check here for the details, it will be helpful to understand different properties while using services.