angular / components

Component infrastructure and Material Design components for Angular
https://material.angular.io
MIT License
24.35k stars 6.74k forks source link

feat(SnackBar): allow to define a container in which to render the snackbar #7764

Open mzernetsch opened 7 years ago

mzernetsch commented 7 years ago

Bug, feature request, or proposal:

feature request

What is the expected behavior?

There should be a setting to define a container in which the snackbar is positioned in.

What is the current behavior?

SnackBar is always positioned based on the viewport.

What are the steps to reproduce?

https://stackblitz.com/edit/angular-material2-issue-ddtruw

What is the use-case or motivation for changing an existing behavior?

I have an app with a static main toolbar at the top. When i position the snackbar to "top right" it will be shown above the main toolbar (see stackblitz example). I would like it to be shown at the top right of the container below the main toolbar (".basic-container" in my stackblitz example).

Which versions of Angular, Material, OS, TypeScript, browsers are affected?

angular 4.4.2 angular-material 2.0.0 - Beta12 Chrome 61.0.3163.100

Is there anything else we should know?

MatSnackBarConfig has a viewContainerRef property. At first i thought this would be exactly what i need but when i tried to change it, it didn't seem to do anything. In the documentation of the MatDialog component (which also has a viewContainerRef property) it is stated that this property does not affect the place where it is rendered. I assume the same goes for MatSnackBar.

willshowell commented 7 years ago

I think this should be possible by providing a custom overlay container, but I've also never tried it.

See https://github.com/angular/material2/blob/master/src/cdk/overlay/overlay.md#the-overlay-container

willshowell commented 7 years ago

@mzernetsch here's an example of this working. I think the css could be cleaner, but it should get you started!

https://stackblitz.com/edit/angular-material2-issue-ansnt5?file=app%2Fapp.module.ts

mzernetsch commented 7 years ago

Thanks for the example. This approach solves the problem with the snackbar but creates a new problem with the dialog component.

Dialogs and SnackBars use the same OverlayContainer, so when i open a dialog while using a custom OverlayContainer it will also be positioned within this custom container: https://stackblitz.com/edit/angular-material2-issue-qc4fq9

Maybe there could be a "DialogOverlayContainer" and a "SnackBarOverlayContainer" which both default to "OverlayContainer". A developer could then define a custom provider as needed for a specific component.

willshowell commented 7 years ago

Hm yeah. I feel like there is a better answer than having all the specific providers (arguably also be needed for select, menu, etc), but my working DI knowledge isn't able to come up with anything.

willshowell commented 7 years ago

@mzernetsch maybe https://github.com/angular/material2/issues/4612#issuecomment-340049922 could help!

Shinigami92 commented 6 years ago

I resolved it the way like https://github.com/angular/material2/issues/7764#issuecomment-337335591

Create the class AppOverlayContainer (src/app-overlay-container.ts)

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

export class AppOverlayContainer extends OverlayContainer {
  constructor() {
    super(undefined);
  }

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

    const element: Element | null = document.querySelector('.mat-sidenav-container');
    if (element !== null) {
      element.appendChild(container);
      this._containerElement = container;
    }
  }
}

Then add { provide: OverlayContainer, useFactory: (): AppOverlayContainer => new AppOverlayContainer() } to your @NgModule providers

This will place all snackbars below the toolbar and you can scroll in the .mat-sidenav-content without loosing the snackbar

Last tested in Angular 6.1.2 and Material2 6.4.3

Edit 1: Use the following class in style.scss

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

Has there been a proper fix for this implemented? I see the "viewContainerRef" but not sure about that, as it doesn't seem the intended result.

thw0rted commented 5 years ago

It's been noted elsewhere that the VCR determines where in the Angular component tree the overlay component lives, for purposes of change detection etc, but does not impact where in the DOM the overlay element gets inserted. IIRC the stated reason for this is that Snackbar is intended to always work at body level, even in cases where the Angular app might coexist with non-Angular content and thus not occupy the entire viewport. I believe the intended solution is the use of a custom Overlay provider but I have yet to see a good example in the official docs.

I personally would like to see Snackbar updated to allow explicit specification of an Overlay instance just for use by the Snackbar service, or even an open method option that says "show this Snackbar in this Overlay, not the default one", but I have no reason to think that's coming any time soon.

totszwai commented 5 years ago

Please provide an official way of positioning the mat-snack-bar within a different container other than body.

totszwai commented 5 years ago

Actually, going through the MatSnackBarConfig (https://material.angular.io/components/snack-bar/api#MatSnackBarConfig), we should have been able to pass a ViewContainerRef for the overlay to attach to, but is not being used by SnackBar not Overlay. Tracing through the code in MatSnackBar's _createOverlay it never even tried to do anything with the provided ViewContainerRef, and the Overlay didn't bother reading it there?

The positionStrategy is hardcoded to use global...

 var positionStrategy = this._overlay.position().global();
totszwai commented 5 years ago

The solution mentioned by @willshowell and @Shinigami92 will not work for dynamically created content... it banks on the fact that you have a way to finding your desire element based on id or a unique element class in your application...

mmalerba commented 4 years ago

The spec does seem to show putting the snackbar over the content, but above app chrome like the the bottom nav and fab (https://material.io/components/snackbars/#placement), this may be a good argument for adding this feature