Angular Material Dialog panelClass Wont Update - css

I'm working with an Angular Material Dialog Box and I'm trying to make the background a custom color.
This question has been asked quite a few times and I've tried to apply the answer but it doesn't seem to work. Specifically, it doesn't appear that the panelClass of the dialog container is updating. Below is the component opening the dialog, the _theming.scss file, and the HTML element
import { Component, OnInit} from '#angular/core';
import { AuthService } from 'src/app/AuthenticationPackage/core/auth.service'
import { MatDialog, MatDialogConfig } from '#angular/material';
import { FactiondialogComponent } from './factiondialog/factiondialog.component';
#Component({
selector: 'app-factions2',
templateUrl: './factions2.component.html',
styleUrls: ['./factions2.component.scss']
})
export class Factions2Component implements OnInit {
constructor( public authService: AuthService,
public dialog: MatDialog ) { }
ngOnInit(){ }
openDialog(faction): void{
const dialogConfig = new MatDialogConfig()
dialogConfig.disableClose = true;
dialogConfig.autoFocus = true;
dialogConfig.data = {faction};
dialogConfig.panelClass = ".faction-dialog";
const dialogRef = this.dialog.open(FactiondialogComponent, dialogConfig)
dialogRef.afterClosed().subscribe(result => {
console.log("dialog closed");
});
}
}
The _theming.scss section:
#mixin mat-dialog-theme($theme) {
$background: map-get($theme, background);
$foreground: map-get($theme, foreground);
.faction-dialog{
background-color:rgb(28, 31, 32)
}
.mat-dialog-container {
#include _mat-theme-elevation(24, $theme);
background: mat-color($background, dialog);
color: mat-color($foreground, text);
}
}
#mixin mat-dialog-typography($config) {
.mat-dialog-title {
#include mat-typography-level-to-styles($config, title);
}
}
This is the markup generated and but it does not include my custom class.
<mat-dialog-container aria-modal="true" class="mat-dialog-container ng-tns-c12-2 ng-trigger ng-trigger-dialogContainer ng-star-inserted" tabindex="-1" id="mat-dialog-0" role="dialog" style="transform: none;"><!--bindings={
"ng-reflect-portal": ""
}--><app-factiondialog _nghost-lda-c13="" class="ng-star-inserted"><div _ngcontent-lda-c13="" class="dialogCard"><h2 _ngcontent-lda-c13="" class="mat-dialog-title">The Harpers</h2><mat-dialog-content _ngcontent-lda-c13="" class="mat-typography mat-dialog-content"><p _ngcontent-lda-c13="">
SOME CONTENT THAT DOESNT MATTER TO THE EXAMPLE
</p></mat-dialog-content><mat-dialog-actions _ngcontent-lda-c13="" align="end" class="mat-dialog-actions"><button _ngcontent-lda-c13="" mat-button="" mat-dialog-close="" class="mat-button mat-button-base" ng-reflect-dialog-result="" type="button"><span class="mat-button-wrapper">Close</span><div class="mat-button-ripple mat-ripple" matripple="" ng-reflect-centered="false" ng-reflect-disabled="false" ng-reflect-trigger="[object HTMLButtonElement]"></div><div class="mat-button-focus-overlay"></div></button></mat-dialog-actions></div></app-factiondialog></mat-dialog-container>
I believe the upper section should say:
<mat-dialog-container aria-modal="true" class="mat-dialog-container faction-dialog ng-tns-c12-2 ng-trigger ng-trigger-dialogContainer ng-star-inserted" tabindex="-1" id="mat-dialog-0" role="dialog" style="transform: none;">
but I'm not sure since I haven't gotten this to work. I've followed the documentation:
https://material.angular.io/components/dialog/api#MatDialogConfig
I'm not sure if there's something I need to add in my app module or somewhere else.
Per the request of Mr. Khan:
faction2.component.ts:
openDialog(faction): void{
const dialogRef = this.dialog.open(FactiondialogComponent, {panelClass: 'faction-dialog'})
dialogRef.afterClosed().subscribe(result => {
console.log("dialog closed");
});
}
Screen with modal open:
HTML Inspect Element
<mat-dialog-container aria-modal="true" class="mat-dialog-container ng-tns-c12-4 ng-trigger ng-trigger-dialogContainer ng-star-inserted" tabindex="-1" id="mat-dialog-2" role="dialog" style="transform: none;"><!--bindings={
"ng-reflect-portal": ""
}--><app-factiondialog _nghost-yis-c13="" class="ng-star-inserted"><div _ngcontent-yis-c13="" class="dialogCard"><h2 _ngcontent-yis-c13="" class="mat-dialog-title"></h2><mat-dialog-content _ngcontent-yis-c13="" class="mat-typography mat-dialog-content"><p _ngcontent-yis-c13=""></p></mat-dialog-content><mat-dialog-actions _ngcontent-yis-c13="" align="end" class="mat-dialog-actions"><button _ngcontent-yis-c13="" mat-button="" mat-dialog-close="" class="mat-button mat-button-base" ng-reflect-dialog-result=""><span class="mat-button-wrapper">Close</span><div class="mat-button-ripple mat-ripple" matripple=""></div><div class="mat-button-focus-overlay"></div></button></mat-dialog-actions></div></app-factiondialog></mat-dialog-container>

If you want to add your own custom class to style the material modal, then firstly passes your custom class to the panelClass key in your modal this way:
this.dialogRef = this.dialog.open(AddCustomComponent,{
panelClass: 'custom-dialog-container', //======> pass your class name
});
this.dialogRef.afterClosed();
Once that's done, all you gotta do is style your modal by using your class and other models won't be affected. For example, you can remove the padding and margin this way.
/*Material Dialog Custom Css*/
.custom-dialog-container .mat-dialog-container{
padding: 0;
}
.custom-dialog-container .mat-dialog-container .mat-dialog-content{
margin: 0;
}
/*---------------------------*/

The panelClass gets added to the parent of the dialog container, so the space is what I was missing:
.custom-dialog-container <<space>> .mat-dialog-container{
padding: 0;
}
Also use ng-deep or put the style in the root stylesheet, not the component

Great answer above, but I wanted to just extend it by saying that the MatDialogConfig can also be passed via some config object.
i.e. I passed in custom class SingleViewDialog for my scenario, where the my-custom-maximize padding override was added to our mat-dialog.scss override file:
export interface SingleViewDialog {
examUID: string;
examData: TrendOctExam;
layoutMode: Layout;
}
public launchSingleView(examUID: string, examData: TrendOctExam) {
const config: MatDialogConfig<SingleViewDialog> = {};
config.hasBackdrop = true;
config.disableClose = false;
config.panelClass = 'my-custom-maximize';
const mode: Layout = 'volume2d';
config.data = { examUID, examData, layoutMode: mode};
const dialogRef = this.matDialog.open(OCTThreeDSingleComponent, config ); //*** INJECT HERE ***
dialogRef.afterClosed().subscribe(_ => this.viewStateService.updateLayoutMode('glaucoma'));
}

Related

Hide web component until browser knows what to do with it

Similar to this question:
How to prevent flickering with web components?
But different in that I can't just set the inner HTML to nothing until loaded because there is slotted content, and I don't wish to block rendering the page while it executes the web component JS.
I thought I could add CSS to hide the element, and then the init of the webcomponent unhides itself, but then that CSS snippet needs to included where ever the web component is used, which is not very modular, and prone to be forgotten
I am working on modal component, here's the code (although I don't think its particularly relevant:
<div id="BLUR" part="blur" class="display-none">
<div id="DIALOGUE" part="dialogue">
<div id="CLOSE" part="close">
X
</div>
<slot></slot>
</div>
</div>
const name = "wc-modal";
const template = document.getElementById("TEMPLATE_" + name);
class Component extends HTMLElement {
static get observedAttributes() { return ["open"]; } // prettier-ignore
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
connectedCallback() {
if (this.initialised) return; // Prevent initialising twice is item is moved
this.setupEventListners();
this.init();
this._upgradeProperty("open");
this.initialised = true;
}
init() {}
get(id) {
return this.shadowRoot.getElementById(id);
}
_upgradeProperty(prop) {
/*
Setting a property before the component has loaded will result in the setter being overriden by the value. Delete the property and reinstate the setter.
https://developers.google.com/web/fundamentals/web-components/best-practices#lazy-properties
*/
if (this.hasOwnProperty(prop)) {
let value = this[prop];
delete this[prop];
this[prop] = value;
}
}
// Setup Event Listeners ___________________________________________________
setupEventListners() {
this.get("CLOSE").addEventListener("click", () => this.removeAttribute("open"));
this.get("BLUR").addEventListener("click", () => this.removeAttribute("open"));
// If the dialogue does not handle click, it propagates up to the blur, and closes the modal
this.get("DIALOGUE").addEventListener("click", (event) => event.stopPropagation());
}
// Attributes _____________________________________________________________
attributeChangedCallback(name, oldValue, newValue) {
switch (name) {
case "open":
// Disabled is blank string for true, null for false
if (newValue === null) this.hideModal();
else this.showModal();
}
}
// Property Getters/Setters _______________________________________________
get open() { return this.hasAttribute("open"); } // prettier-ignore
set open(value) { value ? this.setAttribute("open", "") : this.removeAttribute("open"); } // prettier-ignore
// Utils & Handlers _______________________________________________________
showModal() {
this.get("BLUR").classList.remove("display-none");
// Disable scrolling of the background
document.body.style.overflow = "hidden";
}
hideModal() {
this.get("BLUR").classList.add("display-none");
// Renable scrolling of the background
document.body.style.overflow = "unset";
}
}
window.customElements.define(name, Component);
Q: How do I hide a web component until the browser knows what to do with it?
A: Here's a solution with outside CSS. Make use of the :defined pseudo class:
class X extends HTMLElement {
constructor() {
super().attachShadow({mode: 'open'}).append(document.createElement('slot'));
}
}
foo.onclick = () => {
customElements.define('ab-cd', X);
foo.disabled = true;
foo.textContent = 'registered!';
}
ab-cd:not(:defined) { display: none; }
<ab-cd>text</ab-cd>
<button id="foo">click to register component</button>
I have tried to see where :defined can cause a FOUC
Only when you apply the display:none too late
<my-element>:not(:defined) { display:none }</my-element>
<style>
my-element:not(:defined) {
border: 2px solid red;
}
my-element:defined {
background: pink;
}
</style>
<style id="STYLE"></style>
<button id="BTN_STYLE">click to style component</button>
<button id="BTN_DEFINE">click to register component</button>
<script>
BTN_STYLE.onclick = () => {
STYLE.innerHTML = `my-element:not(:defined) {display:none}`;
BTN_STYLE.remove();
}
BTN_DEFINE.onclick = () => {
customElements.define('my-element', class extends HTMLElement {
constructor() {
super().attachShadow({mode: 'open'}).innerHTML = `constructed`;
}
connectedCallback(){
setTimeout(() => this.shadowRoot.innerHTML = `connected after 3s`,3e3);
}
});
BTN_DEFINE.remove();
}
</script>

How to dynamically load CSS with Angular

For the last couple of days I've been trying several answers, suggestions and tutorials for the problem, but unfortunately non of them did the trick.
The closest one was this:
https://juristr.com/blog/2019/08/dynamically-load-css-angular-cli/
But it uses "extractCss" which has been deprecated since the article has been published.
According to the article:
"styles.js" file should disappear in the Inspector > Network > JS
Clicking the button should add its css file in Inspector > Network > CSS
But neither of these two is happening at the moment.
app.component.ts
const head = this.document.getElementsByTagName('head')[0];
console.log(head);
let themeLink = this.document.getElementById(
'client-theme'
) as HTMLLinkElement;
if (themeLink) {
themeLink.href = styleName;
} else {
const style = this.document.createElement('link');
style.id = 'client-theme';
style.href = `${styleName}`;
head.appendChild(style);
}
}
app.component.html
<head>
</head>
<body>
<button type="button" (click)="loadStyle('client-a-style.css')">STYLE 1</button>
<button type="button" (click)="loadStyle('client-b-style.css')">STYLE 2</button>
</body>
</html>
angular.json
"styles": [
"src/styles.css",
{
"input": "src/client-a-style.css",
"bundleName": "client-a",
"inject": false
},
{
"input": "src/client-b-style.css",
"bundleName": "client-b",
"inject": false
}
These are the main parts of my code.
Hopefully I've explained the problem sufficiently.
Thank you for helping!
You can put your additionals .css in the folder assets (and remove from angular.json)
Then the only change is add the "assets" folder to the href
loadStyle(styleName: string) {
const head = this.document.getElementsByTagName('head')[0];
let themeLink = this.document.getElementById(
'client-theme'
) as HTMLLinkElement;
if (themeLink) {
themeLink.href = `assets/${styleName}`; //<--add assets
} else {
const style = this.document.createElement('link');
style.id = 'client-theme';
style.rel = 'stylesheet';
style.type = 'text/css';
style.href = `assets/${styleName}`; //<--add assets
head.appendChild(style);
}
}
a stackblitz
I think you are missing a property in the link tag, add this to the place you create the link element and it should work.
style.rel = 'stylesheet';
One of possible solution to this task:
import { DOCUMENT } from '#angular/common';
import { Inject, OnDestroy, OnInit, Renderer2 } from '#angular/core';
export class MyComponent implements OnInit, OnDestroy {
private style?: HTMLLinkElement;
constructor(
#Inject(DOCUMENT) private document: Document,
private renderer2: Renderer2,
) {}
public ngOnInit(): void {
const cssPath = '/link/to/style.css';
// Create a link element via Angular's renderer to avoid SSR troubles
this.style = this.renderer2.createElement('link') as HTMLLinkElement;
// Add the style to the head section
this.renderer2.appendChild(this.document.head, this.style);
// Set type of the link item and path to the css file
this.renderer2.setProperty(this.style, 'rel', 'stylesheet');
this.renderer2.setProperty(this.style, 'href', cssPath);
}
public ngOnDestroy(): void {
// Don't forget to remove style after component destroying
this.renderer2.removeChild(this.document.head, this.style);
}
}
If and if your css-files are on the server, so you probably should update your proxy.conf.json file to have access this file from localhost while serve mode is on.

Change ion-backdrop opacity?

I'm trying to change the opacity of my ion-backdrop from 0.08 to 0.33.
I've tried:
ion-backdrop {
opacity: 0.33 !important;
}
and setting $popover-ios-background: rgba(0, 0, 0, 0.33);.
Setting the value on ion-backdrop does work but since it's important, it doesn't animate the fade out.
How can I change the opacity of the backdrop?
I know I am a bit late to this party, but now with Ionic 5, you have a CSS selector that will do the job for you. That is mentioned in their documentation as well.
So basically all you could do is, initialize the modal and style it in your SCSS file.
This is my component.ts file:
import { Component } from '#angular/core';
import { ModalController } from '#ionic/angular';
// ModalComponent is just a normal angular component, your path may vary
import { ModalComponent } from '../../modals/modal.component';
#Component({
selector: 'some-component',
templateUrl: './some-component.component.html',
styleUrls: ['./some-component.component.scss']
})
export class SomeComponentComponent {
constructor(
private modalController: ModalController,
) { }
async presentModal() {
const modal = await this.modalController.create({
component: ModalComponent,
cssClass: 'modal-class'
});
return await modal.present();
}
}
and my component.scss file:
.modal-class {
ion-backdrop {
--backdrop-opacity: 0.33;
}
}
I’ve do it using the cssClass property in alertController (Ionic 4)
async alertError(message: string) {
const alert = await this.alertController.create({
cssClass: 'alertClass',
animated: true,
header: 'Error',
message,
buttons: ['OK']
});
await alert.present();
}
ion-alert {
&.alertClass{
background: rgb(0,0,0,.8);
}
}
I am guessing that this ion-backdrop question it's related with the Ionic Alert Controller. If that is the case than you need to apply CSS inside the global.scss (Ionic 3) file or theme\variable.scss (Ionic 4/5). This is required because ion-backdrop lives in the app as an Ionic Global Component.
Therefore find the mentioned file inside your Ionic project. It's usually in this directory app > src > global.scss.
Now let's suppose that we have this Alert Controller instanciated in some page class.
...
async serviceErrorAlert() {
const alert = await this.alertController.create({
cssClass: 'try-again-alert',
...
});
await alert.present();
}
...
As you can see this Alert Controller haves a CSS class of try-again-alert.
So to add all custom CSS that you want just go the style file and add your own style.
global.scss (Ionic 3):
.try-again-alert {
--background: rgba(55, 67, 77, 0.9);
}
theme\variable.scss (Ionic 4/5):
I strongly recommend you to use CSS background attribute and rgba() property. With this approach you can now choose the color that you want (first three numbers) and the opacity of the color (fourth number).
There is currently an open issue about this in Ionic's GitHub. The only workaround listed there that doesn't break the animation is long and complex - too much to list here. A direct link to the solution: https://github.com/ionic-team/ionic/issues/9105#issuecomment-375010398
I only managed to do it in Ionic 5 by using background: rgba() property with a desired alpha value.
Page where modal is called
openModal(): Promise<void> {
return this.modalCtrl.create({
component: ModalPage,
backdropDismiss: true,
cssClass: 'custom-class'
}).then(modal => {
modal.present();
});
}
app/theme/variable.css
.custom-class {
background: rgba(0,0,0,0.8); /*black with 0.8 opacity*/
}

Vue - bind a css class name with a property value

I would like to set a class to a child component based on in which parent component I am using it. So, for example, I have a dropdown menu, that I would like to use in more components, but I would like to give it a different class based on in which component I am using it.
Something like this, parent component top-bar:
<dropdown-menu :menu="link" :parent:'top-bar'></dropdown-menu>
And then in the dropdown-menu component:
<div class="dropdown" :class="{ parent: parent }">
<script>
export default {
name: 'dropdown-menu',
props: ['parent'],
But, that is not working, how can I do this?
You had a typo :parent:'top-bar' -> :parent='top-bar' and your class binding would always pass the 'parent' string as a class. Learn more here.
I also made a small working example:
Vue.component('parent1', {
template: '<div><dropdown-menu :menu="link" :parent="top_bar"></dropdown-menu></div>',
data () {
return {
link: 'a link',
top_bar: 'parent1'
}
}
});
Vue.component('parent2', {
template: '<div><dropdown-menu :menu="link" :parent="top_bar"></dropdown-menu></div>',
data () {
return {
link: 'another link',
top_bar: 'parent2'
}
}
});
Vue.component('dropdown-menu', {
template: '<div class="dropdown" v-bind:class="parent">{{ menu }}</div>',
props: ['parent', 'menu']
});
var vm = new Vue({
el: '#app'
});
.parent1 {
color: red;
}
.parent2 {
color: blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.js"></script>
<div id="app">
<parent1></parent1>
<parent2></parent2>
</div>

How to have sub components CSS class react on parent components CSS class?

I am trying to get my head around a scenario with CSS components:
I have a react component that uses its own classes. This component has a little helper subcomponent that also has its own classes. Now: When a specific state in the main component is set and a specific class is applied then the helper component's css should react on that class.
For instance:
Component A uses Component B to show something.
Component A gets clicked on and react sets a "clicked"-class on that component
Component B should then visually react on that class
In plain CSS (or similar) I would do this:
Component A:
.component {
height: 10px;
}
.component.clicked {
height: 5px;
}
Component B
.clicked {
.subComponent {
background-color: orange;
}
}
I know that there is a react way to do this. This kind of thing should be done with states and props which are being passed between the components so that this kind of situation gets avoided altogether. But I am currently refacturing a project that still has these issues and I don't really get how to do this properly with react-css-modules.
By the way: My current workaround uses :global but I'd really, really like to avoid this...
Component B:
.clicked:onclick, .subComponent {
// code ...
}
This should do it.
If not I'm just bad at css, or confused about your question.
Parent:
var ComponentA = React.createClass({
getInitialState: function() {
return {
isClicked: false
}
},
onClick: function() {
this.setState({ isClicked: !this.state.isClicked });
}),
render() {
return (
<div className={this.state.isClicked ? "component clicked" : "component"}>
<ComponentB isClicked={this.state.isClicked}/>
</div>
);
}
});
Child:
var ComponentB = React.createClass({
getDefaultProps: function() {
return {
isClicked: false
}
},
render() {
return (
<div className={this.props.isClicked ? "subComponent clicked" : "subComponent"}>
I am the subComponent
</div>
);
}
});

Resources