是否可以在特定的 DOM 元素上打开 Angular/Material2 Snackbar?

Dan*_*iaz 5 angular-material2 angular angular-material-6

组件: https: //material.angular.io/components/snack-bar/examples

我有一个页面是父部分('#wrapper_container')。我想在“#wrapper_element”的子元素的中心弹出小吃栏。

谢谢

and*_*tor 7

我们不能使用viewContainerRef选项将小吃栏附加到自定义容器。从 Angular Material文档来看,这个选项是:

出于依赖注入的目的,充当小吃栏父级的视图容器。###注意:这不会影响小吃栏在 DOM 中的插入位置。###

ComponentPortal 文档中,我们可以找到更详细的描述viewContainerRef

附加组件应该位于 Angular 的逻辑组件树中。这与组件渲染的位置不同,这是由PortalOutlet决定的。当主机位于 Angular 应用程序上下文之外时,原点是必需的。

打开小吃栏时检查 DOM 时,我们会发现以下组件层次结构:

  1. Body-<body>
  2. OverlayContainer-<div class="cdk-overlay-container"></div>
  3. Overlay-<div id="cdk-overlay-{id}" class="cdk-overlay-pane">
  4. ComponentPortal-<snack-bar-container></snack-bar-container>
  5. Component- 我们的Snackbar,打开方式如下:this.snackbar.open(message, action)

现在,我们真正需要更改的是 DOM 中的位置OverlayContainer,根据docs,它是:

渲染所有单独覆盖元素的容器元素。

默认情况下,覆盖容器直接附加到文档正文。

从技术上讲,可以通过使用自定义提供程序来提供自定义覆盖容器,如文档中所述:

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

这里发布的是一个简单的实现。


但不幸的是,它是一个单例并产生了一个潜在的问题:

MatDialogMatSnackbarMatTooltipMatBottomSheet所有使用 的组件都OverlayContainer将在此 AppOverlayContainer 中打开

由于这种情况远非理想,所以我编写的自定义提供程序 ( ) 需要更改only ondemandAppOverlayContainer的位置。overlaycontainer如果不调用,它将让overlaycontainerto 附加到body.

代码是:

import { Injectable } from '@angular/core';
import { OverlayContainer } from '@angular/cdk/overlay';

@Injectable({
 providedIn: 'root'
})
export class AppOverlayContainer extends OverlayContainer {
  appOverlayContainerClass = 'app-cdk-overlay-container';

  /**
   * Append the OverlayContainer to the custom wrapper element
   */
  public appendToCustomWrapper(wrapperElement: HTMLElement): void {
    if (wrapperElement === null) {
      return;
    }
    
    // this._containerElement is 'cdk-overlay-container'
    if (!this._containerElement) {
      super._createContainer();
    }

    // add a custom css class to the 'cdk-overlay-container' for styling purposes
  this._containerElement.classList.add(this.appOverlayContainerClass);
   
    // attach the 'cdk-overlay-container' to our custom wrapper
    wrapperElement.appendChild(this._containerElement);
  }

  /**
   * Remove the OverlayContainer from the custom element and append it to body
   */
  public appendToBody(): void {
    if (!this._containerElement) {
     return;
    }

    // remove the custom css class from the 'cdk-overlay-container'
    this._containerElement.classList.remove(this.appOverlayContainerClass);

    // re-attach the 'cdk-overlay-container' to body
    this._document.body.appendChild(this._containerElement);
  }

}
Run Code Online (Sandbox Code Playgroud)

因此,在打开小吃店之前,我们必须调用:

AppOverlayContainer.appendToCustomWrapper(HTMLElement).
Run Code Online (Sandbox Code Playgroud)

方法,它将 附加overlaycontainer到我们的自定义包装元素。

当小吃店关闭时,最好致电:

AppOverlayContainer.appendToBody();
Run Code Online (Sandbox Code Playgroud)

方法,该方法从我们的自定义包装元素中删除overlaycontainer并将其重新附加到body.

另外,由于AppOverlayContainer将被注入到我们的组件中并且需要保持单例,因此我们将使用useExisting语法提供它:

providers: [
   {provide: OverlayContainer, useExisting: AppOverlayContainer}
]
Run Code Online (Sandbox Code Playgroud)

例子:

如果我们想在自定义容器中显示小吃栏, #appOverlayWrapperContainer1

<button mat-raised-button 
  (click)="openSnackBar(appOverlayWrapperContainer1, 'Snackbar 1 msg', 'Action 1')">
  Open Snackbar 1
</button>
<div class="app-overlay-wrapper-container" #appOverlayWrapperContainer1>
  Snackbar 1 overlay attached to this div
</div>
Run Code Online (Sandbox Code Playgroud)

在我们的组件中,.ts我们有:

import { AppOverlayContainer } from './app-overlay-container';

export class SnackBarOverviewExample {

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

  openSnackBar(
    overlayContainerWrapper: HTMLElement, 
    message: string, 
    action: string
  ) {

    if (overlayContainerWrapper === null) {
      return;
    }
  
  this.appOverlayContainer.appendToCustomWrapper(overlayContainerWrapper);

    this.snackbarRef = this._snackBar.open(message, action, {
      duration: 2000
    });
  }
}
Run Code Online (Sandbox Code Playgroud)

另外,我们需要一些必须插入到应用程序通用样式表中的 css:

.app-cdk-overlay-container {
  position: absolute;
}
Run Code Online (Sandbox Code Playgroud)

完整的代码和演示位于 Stackblitz 上,我已经实现了多个小吃栏容器:

https://stackblitz.com/edit/angular-ivy-wxthzy


joh*_*667 2

是的。viewContainerRef的属性将MatSnackBarConfig接受 aViewContainer作为附加它的位置Portal

获取ViewContainerRef元素的 a 可以通过 ViewChild 查询或将其注入到放置在元素上的指令中来完成。前者是一个更简单的选择:

组件.html:

<div #wrapperContainer> </div>
Run Code Online (Sandbox Code Playgroud)

组件.ts:

@ViewChild('wrapperContainer', { read: ViewContainerRef }) container: ViewContainerRef;


ngAfterViewInit() {

  this.something = this.snackBarService.open(
      'message text',
      'button text',
      { viewContainerRef: this.container}
  );

}
Run Code Online (Sandbox Code Playgroud)