component: https://material.angular.io/components/snack-bar/examples
I have a page that is a parent section ('#wrapper_container'). And I want to popup the snack bar in the centre of a child element of '#wrapper_element'.
Thanks
component: https://material.angular.io/components/snack-bar/examples
I have a page that is a parent section ('#wrapper_container'). And I want to popup the snack bar in the centre of a child element of '#wrapper_element'.
Thanks
We can't use viewContainerRef
option in order to attach the snackbar to a custom container. From Angular Material docs, this option is:
The view container that serves as the parent for the snackbar for the purposes of dependency injection. ###Note: this does not affect where the snackbar is inserted in the DOM.###
From ComponentPortal
documentation, we can found a more detailed description of viewContainerRef
:
Where the attached component should live in Angular's logical component tree. This is different from where the component renders, which is determined by the PortalOutlet. The origin is necessary when the host is outside of the Angular application context.
When inspecting the DOM when a snackbar is opened, we'll find the following component hierarchy:
Body
- <body>
OverlayContainer
- <div class="cdk-overlay-container"></div>
Overlay
- <div id="cdk-overlay-{id}" class="cdk-overlay-pane">
ComponentPortal
- <snack-bar-container></snack-bar-container>
Component
- our Snackbar
, which is opened like: this.snackbar.open(message, action)
.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, by using a custom provider:
@NgModule({
providers: [{provide: OverlayContainer, useClass: AppOverlayContainer}],
// ...
})
export class MyModule { }
A simple implementation would be the one posted here.
But, unfortunately, it is a singleton and creates a potential problem:
MatDialog
, MatSnackbar
, MatTooltip
, MatBottomSheet
and all the components that use the OverlayContainer
will open within this AppOverlayContainer:
Since this situation is far from ideal, the custom provider that I've wrote (AppOverlayContainer
) needs to change the position of the overlaycontainer
only on demand. If not called, it will let the overlaycontainer
to be appended to the body
.
The code is:
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);
}
}
So, before opening a snackbar, we must call:
AppOverlayContainer.appendToCustomWrapper(HTMLElement).
method, which attaches the overlaycontainer
to our custom wrapper element.
When snackbar closes, it is ideal to call:
AppOverlayContainer.appendToBody();
method, which removes the overlaycontainer
from our custom wrapper element and re-attaches it to body
.
Also, since AppOverlayContainer
would be injected in our component and needs to remain a singleton, we'll provide it by using useExisting
syntax:
providers: [
{provide: OverlayContainer, useExisting: AppOverlayContainer}
]
Example:
If we want to display the snackbar in a custom container, #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>
In our component .ts
we have:
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
});
}
}
Also, we need some css that must be inserted in the app generic stylesheet:
.app-cdk-overlay-container {
position: absolute;
}
Full code and demo are on Stackblitz, were I've implemented multiple snackbar containers:
Yes. The viewContainerRef
property of MatSnackBarConfig
will accept a ViewContainer
to use as a place to attach it's Portal
onto.
Getting a ViewContainerRef
of an element can be done through a ViewChild query or by injecting it into a directive that you place on the element. The former is an easier option:
component.html:
<div #wrapperContainer> </div>
component.ts:
@ViewChild('wrapperContainer', { read: ViewContainerRef }) container: ViewContainerRef;
ngAfterViewInit() {
this.something = this.snackBarService.open(
'message text',
'button text',
{ viewContainerRef: this.container}
);
}
You should check their API, there's the parameter viewContainerRef
or you can define the height with verticalPosition
.