quirks of using Angular Shadow DOM CSS Selector - javascript

I'm new to Angular, just a question on applying Shadow DOM CSS Selector. Below is some pseudo code:
//root template.html
<div id="first">
<div id="second">
<paProductForm ..."></paProductForm>
<div>
</div>
where paProductForm is the selector of my custom component as:
#Component({
selector: "paProductForm",
templateUrl: "productForm.component.html",
styles: ["/deep/ div { border: 2px black solid; font-style:italic }"]
})
export class ProductFormComponent {
...
}
I was told that /deep is used by a parent component to define styles
that affect the elements in child component templates, so that means in my case, the style should only apply to <div> inside productForm.component.html and also child components if it has.
But it actually setup a global style element in the head of the client side of html as
which means that the style will also apply on "div" elements with id of "first" and "second", which is not what I want and not the goal of /deep as it claims to be?

According To Angular Documentation:
Applying the ::ng-deep pseudo-class to any CSS rule completely disables view-encapsulation for that rule. Any style with ::ng-deep applied becomes a global style. In order to scope the specified style to the current component and all its descendants, be sure to include the :host selector before ::ng-deep. If the ::ng-deep combinator is used without the :host pseudo-class selector, the style can bleed into other components.
/deep and ::ng-deep are the same, except ::ng-deep is supported by SASS, (both of which are marked deprecated and should be avoided)

From: Adam Freeman Book “Pro Angular 9.” :
Using the shadow DOM means that there are boundaries that regular CSS selectors do not operate across. To help address this, there are a number of special CSS selectors that are useful when using styles that rely on the shadow DOM (even when it is being emulated), as described below:
:host
This selector is used to match the component’s host element.
:host-context(classSelector)
This selector is used to match the ancestors of the host element that are members of a specific class.
/deep/ or >>>
This selector is used by a parent component to define styles that affect the elements in child component templates. This selector should be used only when the #Component decorator’s encapsulation property is set to emulated.

Related

Change CSS style for external library's component in react

I am using an external library in react called "drag and drop files"
This library only allows adding children to the component and style them.
My problem is, the library's component style "the parent" has unnecessary style elements, and I'm only able to modify the children and change their style inside the parent element. However, I can't change the element's style itself.
My question is, is there a way to update the parent component style even though there's no direct access to it?
Inspect html elements with your browser.
And you can change the styles in your style.css using classes or elements or ids that you inspected.
sorry don't have enough rep for a comment but I would think that if you can get the name of the parent element and if you use styledcomponents (https://styled-components.com/)
you could just override the elements styling by creating a styledversion of it.
for example an Avatar from mui would be overridden by
const StyledAvatar = styled(Avatar)`
background-color: black;
color: white;
`;
You'd still need to import the element though.
a normal element (div for example) could be styled by doing the following:
const StyledDiv = styled.div`
background-color: black;
color: white;
`;
hope this kinda helps.
Edit: to use your own styled components, don't forget to export them :)

:host-context not working as expected in Lit-Element web component

I've got two Lit-element web components - one is units-list, which contains many units-list-item elements. The units-list-item elements have two different display modes: compact and detailed. Because the list element supports infinite scroll (and thus could contain several thousand units), we need any mechanism that toggles between the two modes to be as performant as possible.
That's why I thought an ideal solution would be to use the :host-context() pseudo-selector in the styles for the units-list-item element, as that way every units-list-item element could switch between the two display modes just by changing the class applied to an ancestor (which would be within the shadow DOM of the units-list element).
To elaborate, here's the relevant markup from the units-list element. Note that the "trigger" classes are being applied to the #list-contents div, which is part of the units-list template.
<div id="list-contents" class="${showDetails ? 'detail-view table' : 'compact-view table'}">
${units.map(unit => html`<units-list-item .unit="${unit}"></units-list-item>`)}
</div>
As you can see, the showDetails flag controls whether the "detail-view" or "compact-view" class is applied to the div containing all of the units-list-item elements. Those classes are definitely being applied correctly.
Here's the full render method from the units-list-item element (unnecessary markup removed):
render() {
const {unit} = this;
// the style token below injects the processed stylesheet contents into the template
return html`
${style}
<div class="row compact">
<!-- compact row markup here -->
</div>
<div class="row detail">
<!-- detail row markup here -->
</div>
`;
}
Then I have the following in the units-list-item element's styles (we're using SCSS, so the single-line comments are not a problem):
// This SHOULD hide the compact version of the row when the
// unit list has a "detail" class applied
:host-context(.detail-view) div.row.compact {
display: none !important;
}
// This SHOULD hide the detail version of the row when the
// unit list has a "compact" class applied
:host-context(.compact-view) div.row.detail {
display: none !important;
}
My understanding of the :host-context selector says that this should work, but Chrome just renders both versions of the row every time, and the Chrome dev tools show that the selectors are never matching with either of the rows.
I know there are several alternatives that would work, but this is the only one I'm aware of that would allow the entire list of units to switch modes by changing a single class on a parent element. Every other solution I've considered would require, at the least, updating the class attribute on every units-list-item element in the list. I'd like to avoid that if possible.
Of course, my primary concern is simply to make this work, if possible, but I'm also curious about a couple of things and can't find any info about them. The two questions I can't seem to find an answer for are
When :host-context is used within an element that is itself part of a shadow DOM, does it consider that parent element's shadow DOM to be the "host context", or does it jump "all the way out" to the document DOM?
If it's the former, will :host-context jump multiple shadow DOM boundaries? Say I have a custom page element that contains a custom list element, which itself contains many custom item elements. If that item element has a :host-context rule, will the browser first scan up the shadow DOM of the list element, then, if matching nothing, scan up the shadow DOM of the page element, and if still matching nothing, then scan up the main document DOM to the <html> tag?
There is no support for :host-context in FireFox or Safari
last update from a month ago is both Mozilla and Apple are not going to implement it.
Looks like it is going to be removed from the spec:
https://github.com/w3c/csswg-drafts/issues/1914
One alternative is to use CSS Properties (those trickle down into
shadowDOM)
JSFiddle: https://jsfiddle.net/WebComponents/hpd6yvxt/
using host-context for Chrome and Edge
using CSS properties for other Browsers
Update Feb 2022
Apple quietly changed their mind? now in Safari TP:
https://caniuse.com/?search=host-context
An example of using css porperties, as Danny Engelman says, to get your goal
customElements.define('list-item', class extends HTMLElement {
constructor() {
const style = document.createElement('style');
const divcompact = document.createElement('div');
divcompact.innerHTML = "compact";
divcompact.className = "compact";
const divdetail = document.createElement('div');
divdetail.innerHTML = "detail";
divdetail.className = "detail";
let shadow = super().attachShadow({
mode: 'open'
});
shadow.append(style, divcompact, divdetail);
style.innerHTML = `
.compact {
background-color: red;
display: var(--display-compact, block);
}
.detail {
background-color: green;
display: var(--display-detail, block);
}
`
}
});
.compact-view {
--display-detail: none;
}
.detail-view {
--display-compact: none;
}
.box {
width: 200px;
height: 50px;
border: solid 1px black;
margin: 5px;
}
<div class="box">
no class applied
<list-item>test</list-item>
</div>
<div class="compact-view box">
compact view
<list-item>test</list-item>
</div>
<div class="detail-view box">
detail view
<list-item>test</list-item>
</div>

How default view encapsulation works in Angular

As we know, default view encapsulation for a component in angular application is Emulated,ie
encapsulation: ViewEncapsulation.Emulated
I really do not understand how it works behind the hood if it is not a shadow dom.
There are three types of encapsulation in angular
ViewEncapsulation.Emulated and this is set by default
ViewEncapsulation.None
ViewEncapsulation.Native
Emulated mode
Assume that you have two different components comp-first and comp-second , For example you define in both of them
<p> Some paragraph </p>
So if you apply some styling for paragraph in comp-first.css
p {
color: blue;
}
and then inspect p element on comp-first.html and look for its styling will find something like this
p[_ngcontent-ejo-1] {
color: blue;
}
"_ngcontent-ejo-1" is just a simple key for differentiate such an element from others components elements
None mode
If you apply this mode to such a component for instance comp-first and then you go and inspect any element it will not provide any attribute like "_ngcontent-ejo-1" to any element , So applying any styling or class it will be provided globally .
Native mode
This should give the same result as if you are using emulated mode but it comes with Shadow DOM technology in browsers which support it
When you write
<div class="XXX"></div>
With the style
XXX { color: red; }
The compiler will translate it to
<div class="XXX" ng_host_c22></div>
With the style
XXX[ng_host_c22] { color: red; }
it simply adds an unique (randomly generated) attribute to your elements and style, to avoid them colluding with other styles.
This means if you declare the class XXX in 2 different components, then they will have a different attribute, and not collude.

How does ng-deep work internally in Angular

I'm trying to figure out how exactly ::ng-deep selector works. How does it omit random nghost and ngcontent attributes names?
Thanks in advance
if you use ::ng-deep in a component where view encapsulation is turned off, it stays there. Since this is invalid CSS, some rules break. It's silent and partial failure because CSS parser simply sees ::ng-deep as unknown selector.
If we want our component styles to cascade to all child elements of a component, but not to any other element on the page, we can currently do so using by combining the :host with the ::ng-deep selector.
:host ::ng-deep h2 {
color: red;
}
FYI: The ::ng-deep pseudo-class selector also has a couple of aliases: >>> and /deep/, and all three are soon to be removed.
https://blog.angular-university.io/angular-host-context/
How and Where to use ::ng-deep?

How to use global css styles in shadow dom

Shadow dom encapsulate css styles, selectors don't cross the shadow boundary.
Question: How to use global common css styles in shadow dom?
(suppose there are some common css styles which will be used across all pages (e.g.: font-family, h1, h2, clear, reset ...), how to make it works in shadow dom?)
I've just struggled with the same problem as an original issue, namely: defining once some global rule for, say, <h3> element and benefit from that within any/many ShadowDOMs.
No, css-variables are not suited well for this thing, since even if I've defined once, say, font and color variables for <h3>, I'll still need to go over each and every shadowed stylesheet and add a CSS rule consuming them.
At the point of writing this (yes, we are 2019 now) the shortest standardized solution is indeed importing some global common CSS. Works perfectly in Chrome, Firefox and Anaheim (Edge on Chromium).
It still requires to add an #import rule in each and every component, so still costy (from coding/maintenance POV, the stylesheet fetched only once), but that's the lowest price we can pay now.
Some solutions:
CSS variables:
http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-201/
https://developers.google.com/web/updates/2016/02/css-variables-why-should-you-care?hl=en
http://blog.chromium.org/2016/02/chrome-49-beta-css-custom-properties.html
:host-context:
http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-201/
Also, I haven't tested, but someone suggested #import url('/common-style.css');, here: Polymer share styles across elements
Please note, one of the articles listed above was also pointed out by Amid. By the time that article was written, Chrome had no CSS variables. But now it already works natively with the recently launched Google Chrome 49.
Non of the provided links work for me in Chrome 66 (as of 2018) so I ended up with this to customize custom element from outside:
<custom-element tabindex=10>
<style>
:host div {
--color: red;
}
</style>
</custom-element>
class Element extends HTMLElement {
constructor() {
super();
var shadow = this.attachShadow({mode: 'open'});
var user_style = shadow.host.innerHTML.match(/<style>([\s\S]*?)<\/style>/);
var style = document.createElement('style');
style.innerHTML = `
:host div {
color: var(--color, #aaa);
}
` + user_style ? user_style[1] : '';
shadow.appendChild(style);
shadow.host.querySelector('style').remove();
}
}
customElements.define(
"custom-element",
Element
)
It is 2022
ShadowDOM is styled by:
<style> within shadowDOM
Inheritable styles
https://lamplightdev.com/blog/2019/03/26/why-is-my-web-component-inheriting-styles/
(cascading) CSS properties
shadowParts (and Themes)
https://meowni.ca/posts/part-theme-explainer/
<slot> are reflected, they are NOT styled by the shadowDOM, but by its container.
See: ::slotted content
(feb 2022) Constructible StyleSheets is still a Chromium only party
https://caniuse.com/mdn-api_cssstylesheet_cssstylesheet
your custom directive
Define styles in a base element and have all elements that need the style inherit from that base element.
With lit, something like this
export class AppComponentBase extends LitElement {
static styles = css`
:host {
font-family: sans-serif;
font-size: 21px;
}
`;
}
And then in stead of inheriting from LitElement, make all components in your application inherit from AppComponentBase like this:
export class HomeRouteComponent extends AppComponentBase {
render() {
return html`
<h1>Home</h1>
<p>
Welcome to my website
</p>
`;
}
}
You can also add or some styles
export class HomeRouteComponent extends AppComponentBase {
static styles = [super.styles, css`
h1 {
color: red;
}
`]
render() {
return html`
<h1>Home</h1>
<p>
Welcome to my website
</p>
`;
}
}
Having a common component to inherit from may have other advantages. For instance to share some logic, although this might better be achieved via controllers.
This is all lit, but the same concept could be implemented with "bare" customElmements with relative ease.
You do it via ::shadow pseudo-element. Like this:
::shadow .redColor
{
background-color: red;
}
That will apply styling to all elements inside shadow trees with .redColor class.
More info + other styling possibilities in this great article: Shadow DOM 201

Categories

Resources