Angular material: snackbar is hidden behind component with high z-index - css

So I'm playing around with Angular a bit and I wanted to add material snackbar to my app for when there's an error in my app.
So, I have my hompage and my navigation is an overlay with a z-index of 3000. In the navigation there's the option to log in ( see picture below ). I entered bad log in data on purpose to trigger the error handler and make the snackbar appear.
The snackbar does appear. However, it is hidden behind the navigation. How can I make it show above the navigation? I tried adding a z-index of 10000 to the scss of the component that handles the snackbar with the following code:
* {
z-index: 10000;
}
and
::root {
z-index: 10000;
}
But none worked. Does anyone know how to do this?
App.component.ts: user-navigation is where I handle the log in. Notifications contains the logic for the snackbar
<navigation></navigation>
<user-navigation>
</user-navigation>
<router-outlet></router-outlet>
<notifications></notifications>
Notifications.component.ts , this works, it opens the snackbar, but it is hidden behind the user navigation
import { Component, OnInit } from '#angular/core';
import {MatSnackBar} from '#angular/material';
import {NotificationService} from '../services/notification.service';
#Component({
selector: 'notifications',
templateUrl: './notifications.component.html',
styleUrls: ['./notifications.component.css']
})
export class NotificationsComponent implements OnInit {
constructor(public snackBar: MatSnackBar, private notificationService: NotificationService) { }
ngOnInit() {
this.notificationService.notification$
.subscribe((message) => {
console.log('received the notification', message);
this.openSnackBar(message);
});
}
openSnackBar(message: string, action?: string) {
setTimeout(() => {
this.snackBar.open(message, action, {
duration: 20000
});
}, 0);
}
}
This is the login page. The home page is behind this and not visible because of the high z-index I gave to the navigation
This is the homepage when I close the navigation. The snackbar is visible, but I want to be able to also see it with the navigation open

(Angular 8)
Putting this on style.css worked fine for me:
.cdk-overlay-container {
position: fixed;
z-index: 10000;
}

you can try with override this css class
style.css/style.scss
.cdk-overlay-pane{
z-index: 10000 !important;
}

(Angular 9) I add the following css code to fix it.
styles.scss
.cdk-overlay-container {
z-index: 99999999999999;
}

I had the same issue. I decided to reduce the z-index of the footer element instead of increase the snack one.
Regards
M

can't you just put the <notifications> element on top?
<notifications></notifications>
<navigation></navigation>
<user-navigation></user-navigation>
<router-outlet></router-outlet>

Try to put this in your base/root css file, if you don't have one, try adding tags in your index.html file and add this css over there.
:host /deep/ .cdk-overlay-pane{
z-index: 1000;
}

I have a problem because while using MatSnackBar and MatDialog since MatSnackBar was always overlaid by dialog. With this solution, I got MatSnackBar with a higher z-index: https://github.com/angular/components/issues/7471#issuecomment-340856500

.cdk-overlay-container { z-index: 100000; }
For Any Angular Version | you can use this,
btw you can increase the size until it gets appeared on top of every elements(I mean your components).

Related

Lit-element - :host selector is not triggering render on Safari

In a project I'm working on, I have this LitElement component, which is absolute-positioned and determines its left or top locations according to its reactive properties.
I've encountered a problem in Safari only, including iOS Safari/Chrome/Firefox. The element has updated its shadow styles, but in the view it does not move at all. I realized its a render issue when I found that when the cursor hovers the element, or exits the browser view, the element pops to the expected location.
I managed to reproduce the problem with a simpler code:
my-elem.ts:
import { LitElement, html, property, customElement, css } from 'lit-element';
#customElement('my-elem')
export class MyElem extends LitElement {
#property({ type: Number, reflect: true })
left: number = 30
static get styles() {
return css`
:host { position: absolute; }
div { position: absolute; }
`;
}
render() {
return html`
<style>
:host { left: ${this.left}px; }
</style>
<div> Hello </div>
`;
}
}
index.html:
<html lang="en">
<head>
<script src="my-elem.ts"></script>
</head>
<body>
<my-elem id="my-elem" left="50"></my-elem>
<button id="move-btn">move</button>
<script>
const elem = document.getElementById('ma-elem');
const moveBtn = document.getElementById('move-btn');
moveBtn.onclick = function() {
elem.left += 30;
}
</script>
</body>
</html>
I found that this happens only on :host selector. If the shadowed styling updates the div's left, it renders without any problems.
I wish to avoid forcing the browser to layout/paint, due to performance issues.
I think the root of your problem is that you are trying to use an expression inside a style element in your render template.
The LitElement guide mentions that using them that way has limitations and can cause performance issues. I think your problem is just facing one of them so you should remove that style element.
As an alternative, since you want to affect the host element, you can actually do this in an easier way if you just don't use a property but style the host directly.
So when using my-elem the code would look like:
<my-elem style="left: 30px;"></my-elem>
This is because styles applied to :host in shadow DOM have less priority than those applied to it using classes or the style attribute by its parent.
Alternatively, if you really want to keep the property, you can create property accessors for the left property and set the style to the host from there like this:
export class MyElem extends LitElement {
// other code
set left(value) {
const oldValue = this.left;
this._left = value;
this.style.setProperty('left', `${value}px`)
this.requestUpdate('left', oldValue);
}
get left() {
return this._left;
}
}

Angular: How to add global CSS (e.g. to the body), but only for one specific page?

How can I add separate CSS for one page in Angular?
This is the CSS I need, as per How to remove the URL from the printing page?:
#media print{
#page{
margin:0;
}
body{
margin:30px;
}
}
But putting CSS into the component with ::ng-deep or ViewEncapsulation.None won't help here, because when navigating away from a page, the CSS of the page isn't deleted.
I've added a Stackblitz, which explains the problem clearly.
I've come up with a potential solution, but it doesn't work:
encapsulation: ViewEncapsulation.None
...
constructor(private renderer: Renderer2) {
this.renderer.addClass(document.body, 'special-print');
}
ngOnDestroy() {
this.renderer.removeClass(document.body, 'special-print');
}
....
....
....
#media print{
#page{
margin:0;
}
body.special-print{
margin:30px;
}
}
Why it doesn't work:
While it would help with <body> CSS, it won't help with #page CSS. Perhaps the question would be better summarized as "How to add global CSS, but remove it when we leave the page?".
Solved!
We print the <style> block directly into the component's HTML, and therefore when the component gets removed, our <style> block gets removed too. (Normally this wouldn't work, but thanks to DomSanitizer.bypassSecurityTrustHtml, Angular won't break our code when running optimizations.)
Here's a StackBlitz.
First, create a new component to handle the work:
component.ts: (This is all we need. We don't need an HTML or style.css file.)
//Inside your local component, place this HTML
//<app-local-css [style]="'body{background:green !important;}'"></app-local-css>
// OR
//<app-local-css [scriptURL]="'/path/to/file.css'"></app-local-css>
#Component({
selector: "app-local-css",
template: '<span style="display:none" [innerHTML]="this.safeString"></span>'
})
export class LocalCSSComponent implements OnInit {
constructor(protected sanitizer: DomSanitizer) {}
#Input() scriptURL?: string;
#Input() style?: string;
safeString: SafeHtml;
ngOnInit() {
if (this.scriptURL) {
let string = '<link rel="stylesheet" type="text/css" href="' + this.scriptURL + '">';
this.safeString = this.sanitizer.bypassSecurityTrustHtml(string);
} else if (this.style) {
let string = '<style type="text/css">' + this.style + "</style>";
this.safeString = this.sanitizer.bypassSecurityTrustHtml(string);
}
}
}
And then use it like this:
mySample.component.html:
<app-local-css [style]="'body{background:green !important;}'"></app-local-css>
// OR
<app-local-css [scriptURL]="'/path/to/file.css'"></app-local-css>
Angular is doing client-side rendering, which is bad news, because you do not have separate pages. You have several possible solutions though:
1. Separate page
You can create another page with or without Angular, which includes the CSS you need and load that page. In the most simplistic approach to achieve this, the other page would have a different URL. If having a different URL is not to your liking, then you could hide your page's content and show the other page inside an iframe. It would admittedly be a hacky solution, but it is a solution.
2. Client-side CSS rendering
Instead of just loading the CSS, you could have a component which would control global CSS rules, matched by your view's name. You would have a template value rendered to a property, like:
#media print{
#page{
margin:0;
}
body{
margin:30px;
}
}
And when you visit the page where this needs to be activated, you would simply initialize a property with a style HTML element that was generated based on the template and added to head. Once you leave the given view, your component would detect that event and would remove() that element. If you choose this solution, then it would be wise to make sure that you are supporting this on more general terms, so that if some new views will have their custom global CSS, then they would be easy to integrate into your project in the future.
3. body classes
You could add/remove some custom-print or whatever class to/from body whenever the style is to be changed. This way you could add the CSS exactly once to your HTML and change the rules accordingly, like:
body.custom-print {
margin: 30px;
}
This would be a neat solution, but the problem in your case is that you have a #page rule as well and I'm not sure how you could make that dependant on body classes or some other HTML attributes. I would conduct quite a few experiments about this if I were you.
4. Iframe staging
You could avoid having that CSS in your main page, but would have a hidden iframe where you would have the CSS and would just copy the content into the CSS and once that's loaded, print that.
Don't change the whole body from apple. Instead, there are a few changes to make.
In the app component, hold a boolean for whether or not you are on apple, and use ngClass for class defined in scss.
Track which route you are on in appComponent, and set isApple accordingly
Add a div around all your html, for container to take full size
Add global html, body setting height to 100% so you see color everywhere
Remove body overriding in apple
so,
appComponent.ts:
isApple: Boolean;
constructor(router: Router) {
router.events.subscribe(v => {
if (v instanceof NavigationEnd) {
this.isApple = v.url === "/apple";
}
});
}
appComponent.html:
<div [ngClass]="{'red':isApple}" class="container">
<p>
There are two components: Apple and Banana. Switching between them will show
the problem.
</p>
<router-outlet></router-outlet>
</div>
appComponent.scss
.red {
background-color: red;
}
.container {
height: 100%;
}
apple.component.scss (remove body)
/*Sample "global" CSS, that affects something outside the current component.*/
::ng-deep {
#media print {
#page {
margin: 0;
}
}
}
styles.scss (global)
html, body {
height: 100%;
}
You can see this altogether at this Stackblitz link
You can add different css files in the component (for instance, app-task.component.ts):
#Component({
selector: 'app-task',
templateUrl: './app-task.component.html',
styleUrls: ['./app-task.component.scss', './styles2.scss', './styles3.scss']
})
In this example, the style files are in the same folder that the component, but this is not the best option: you have to put the files in assets, for example. Also, be careful with the thread of the styles, since the first one you put will be put before the second (obviously).

Angular Material Snackbar position

I'd like to place material snackbar under my header (height of it is 60px).
The problem is that verticalPosition places snackbar to the top and covers header.
I'd tried to add "margin-top: 75px", but the above area was unclickable. (there's an overlay)
I can't change the style of cdk-overlay-pan because i have other dialogs.
You can try this :
Create a global css class to modify style of your Snackbar component.
Set panelClass property in config options.
Add this style declaration in global styles.scss :
.my-custom-snackbar {
margin: 0 !important;
position: absolute;
right: 25px;
top: 60px;
}
Then when SnackBar is opened :
this.snackbar.open('Hello the world!', '', {
panelClass: 'my-custom-snackbar'
});
From the api documentation (https://material.angular.io/components/snack-bar/api):
Parameters for the "Open" method of MatSnackBar:
message (string) - The message to show in the snackbar.
action (string) - The label for the snackbar action.
config? (MatSnackBarConfig<any>) - Additional configuration options for the snackbar.
MatSnackBarConfig has amongst the following properties:
horizontalPosition: MatSnackBarHorizontalPosition - The horizontal position to place the snack bar.
verticalPosition: MatSnackBarVerticalPosition - The vertical position to place the snack bar.
To see that in action, you could easily create a component, inject the MatSnackBar and
write a open method to pass your config.
Here is a little example how my Snackbar component looks like:
import { Component } from '#angular/core';
import { MatSnackBar } from '#angular/material/snack-bar';
#Component({
selector: 'app-mat-snackbar',
templateUrl: './mat-snackbar.component.html',
styleUrls: ['./mat-snackbar.component.scss']
})
export class MatSnackbarComponent {
constructor(public snackBar: MatSnackBar) {}
openSnackBar(message: string, action: string, className: string) {
this.snackBar.open(message, action, {
duration: 9000,
verticalPosition: 'top',
horizontalPosition: 'center',
panelClass: [className],
});
}
}
I call it from my http Interceptor like that:
constructor(private SnackbarComponent: MatSnackbarComponent) {}
...
...
// Something wrong happened
this.SnackbarComponent.openSnackBar(errorMessage, 'Close', 'error-snackbar');
In my Style.css file i added a css class for this snackbar, which is also the last parameter for the openSnackBar method.
This was just a workaround because some css inside my mat-snackbar.component.css does not work. I found some other solution with ::ng-deep here: MatSnackBar panelClass doesnt read styling class.
You have to use it carefully because there is no ViewEncapsulation if you use the style in styles.css. But for me it was much cleaner to do that like this.
my styles.css looks like:
.error-snackbar {
position: absolute;
top: 60px;
}
You need to apply margin-top to the parent div of snack-bar-container element.
Use this CSS (you may need to apply none for the View.Encapsulation depending on your structure.)
.mat-snack-bar-handset {
margin-top: 75px !important;
}

Angular 6: How to change page background-color dynamically

I work on an Angular 6 app (with Bootstrap 4) and need to change the page background color depending on which page the user enters. Default is white, but for login and registration screen the page color needs to be blue.
What I found so far:
in ngAfterViewInit() using
this.elementRef.nativeElement.ownerDocument: this approach makes the
app more vulnerable to XSS attacks and I want to avoid that.
Set View Encapsulation to None in app.component.ts: this way I can
set the body color in the app.component, that is 1 step forward.
So, now I have in my app.component.css:
body {
background-color: blue;
}
Question:
How can I change that color value (in app.component) using a variable?
With [ngStyle] I can not reach the body background-color.
Maybe using a css variable? but how can I change the value of that css variable dynamically?
I'm new to Sass, but might this offer a solution?
My question is different from the other question on this subject as I need to be able tochange the color value dynamically.
use render2 and set class to body using document object
app.component.ts
constructor(private renderer: Renderer2) {
this.renderer.addClass(document.body, 'body-class');
}
Note: if you are toggling classes, just remove previous class before assigning new class
The way I would do it is based on the routes. When defining the routes, you can add extra data, for example a class name.
When the route changes (i.e. via navigation), the data from the active route can be used to set the class on the body tag.
This is how you can achieve this
Update the styles.css to add different classes for body:
body {
...
}
body.red {
background-color: #ff8181;
}
body.blue {
background-color: #a0c3ee;
}
Update the routes to add extra data, specifying the body class name. Add an extra data property, like bodyClass:
const routes: Routes = [
{ path: '', component: DefaultComponent },
{ path: 'red', component: RedComponent, data: { bodyClass: 'red' } },
{ path: 'blue', component: BlueComponent, data: { bodyClass: 'blue' } }
];
Write the code to read the bodyClass and set the class to the body element when navigation occurs. This can be done in the app.component.ts:
#Component({
selector: 'app-root',
template: `
<div>
<router-outlet></router-outlet>
<app-menu></app-menu>
</div>
`
})
export class AppComponent implements OnInit {
constructor(
#Inject(DOCUMENT) private document,
private renderer: Renderer2,
private router: Router,
private activatedRoute: ActivatedRoute) {
}
ngOnInit() {
this.router.events
.pipe(filter((event) => event instanceof NavigationEnd))
.pipe(map(() => this.activatedRoute))
.pipe(map((route) => {
while (route.firstChild) {
route = route.firstChild;
}
return route;
}))
.pipe(filter((route) => route.outlet === 'primary'))
.pipe(mergeMap((route) => route.data))
.subscribe((event) => this.updateBodyClass(event.bodyClass));
}
private updateBodyClass(customBodyClass?: string) {
this.renderer.setAttribute(this.document?.body, 'class', '');
if (customBodyClass) {
this.renderer.addClass(this.document?.body, customBodyClass);
}
}
}
Here is a demo on StackBlitz: https://stackblitz.com/edit/angular-ivy-rs1tai
Why not just define a separate class based on different background-color? For instance:
.blue {
background: blue
}
.green {
background: green
}
.grey {
background: grey
}
And then set these classes on the body using ng-class or ngClass whatever convention you use based on the page. This should be fairly easy to implement.
My favourite approach for doing stuff like this is to add a class to html tag depending on the route. For example, we have some code in our basic layout component (you could put it in your root component) that does this inside ngOnInit:
let wrapper = ''
const path = this.activatedRoute.snapshot.routeConfig.path
wrapper += this.tidyPath(path)
if (wrapper !== '') wrapper += '-'
const childPath = this.activatedRoute.snapshot.firstChild.routeConfig.path
wrapper += this.tidyPath(childPath)
this.routeWrapperCssClass = wrapper
$('html').addClass(this.routeWrapperCssClass)
This will add a class to your html tag to make it look like this (although you may have to tweak this code to suit your app):
<html class="registration">
....
</html>
The class will be instantly updated whenever you change route.
Now you can do this in your main stylesheet:
body {
background-color: pink;
}
html.registration body {
background-color: yellow;
}
You could also do things like hide elements based on the class added to the html tag like so:
.navbar {
display: block;
}
html.registration .navbar {
display: none;
}
Because you know what route you are on at all times you have total control via CSS.
PS you may want to use render2 instead of jQuery to do the DOM manipulation - see this article ... https://alligator.io/angular/using-renderer2 ... never used it myself before but almost identical to jQuery syntax - thanks to Pratap A.K answer for this
Personally i replace :
<body>
<app-root></app-root>
</body>
to
<app-root></app-root>
and then i add all the time the body on components or if i have multi
router-outlets i add it on app.component.css

CSS Styling Angular 2+ (pain in the butt)

Ok, i am new to Angular 4. I am having issues with styling the application properly. I went ahead and added a background image to the body, this worked as expected, then added a component that would display content and it looked as expected.
I added a second component different route but for this one I do not want the body to have the background image at this point i am not sure what is the best practice.
I read a few articles and some say that you can have a different style for the body at the component level by adding a body style override in the styleUrls. I did this but everytime i went from say /myroute/page2 to myroute/page1 the background sticked to what i set for myroute/page2 while it should show the body image for /myroute/page1. I am also using ViewEncapsulation set to None (maybe this is the issue)?.
Also this is my setup and may be wrong too
index.html has something like this:
</head>
<body>
<app-root></app-root>
</body>
</html>
then my app.component.html has this:
<app-navigation></app-navigation>
<router-outlet></router-outlet>
<app-footer></app-footer>
and then i have my welcome.component.html that has a section
<section id="hero">
<div>
</div>
</section>
Now the component above shows the background set correctly, but if I navigate using something like
this.router.navigate(['/page2']);
the background stays the same as in my welcome component. If i refresh on /page2 the right background now shows up.
UPDATE:
Ok i gave up on showing the background on the second component, i wanted without background image, but want to keep the one in the first component. So i removed the ViewEncapsulation from all of them but now if I move the body {} to the first component it does not show it for some reason (I made sure it has the right path). Is there a better fix than removing it by using the DOM?. What is the best practice?.
You need to set style class on ngOnInit and remove it on ngOnDestroy.
constructor(private renderer: Renderer2) {
}
ngOnInit() {
this.renderer.addClass(document.body, 'your_class');
}
ngOnDestroy() {
this.renderer.removeClass(document.body, 'your_class');
}
This worked for me, pls try. In your component1,
ngOnInit() {
document.body.style.background="url(https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSu5rIAFQkkswZLuwAUCXZqc_8bBROGkGgmZP5bmGk57sRKXWJMEg)";
}
ngOnDestroy() {
document.body.style.background="";
}
In your component2,
ngOnInit() {
document.body.style.background="url(http://gkreading.com/wp-content/uploads/2017/03/awesome-kid-in-the-grass.jpg)";
}
ngOnDestroy() {
document.body.style.background="";
}
Hope this helps.

Resources