How default view encapsulation works in Angular - javascript

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.

Related

What do multiple css classes do?

My application uses multiple classes using CSS modules.
open is a boolen variable to determine if Paper should shift or not.
<Paper className={`${classes.paper} ${open && classes.paperShift}`}>
Question: Why are multiple classes being used, and how can they render differing results?
Note, I looked at this other post: using css modules how do I define more than one style name
The rules of each CSS class are simply combined together by the browser and all the rules are applied to the element. (If any of the rules contradict each other, the browser will decide which has precedence - if you learn more about CSS you can start to understand how that works in detail).
So, multiple classes can be used to apply various different styles to an element at the same time. Those rules might be split up into different classes to make the visual design more flexible and the rules more re-usable.
Classes defined on an element can potentially also be used by JavaScript code to select certain elements (or groups of elements) in order to perform actions on them or get information from them.
Those are HTML classes, not CSS classes.
It just means that the element is a member of multiple classes.
Anything (CSS, JS, anything else) that tries to reference the element by either class will find it. Anything that tries to reference by both classes will find it.
console.log(Array.from(document.querySelectorAll('.one')));
console.log(Array.from(document.querySelectorAll('.two')));
console.log(Array.from(document.querySelectorAll('.one.two')));
.one {
font-family: monospace;
font-size: 1.5em;
}
.two {
background: yellow;
}
<div class="one">one</div>
<div class="two">two</div>
<div class="one two">one two</div>

: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 to style css of ionic2-calendar?

I'm working on an app with Ionic 5.0.0, Angular 8 and using the ionic2-calendar plugin. Although the plugin demo works fine, I can't seem to modify the styling of the calendar.
The documentation lists a couple of classes that seem to be used for each element, but adding them to my own scss file and adding !important (or not) doesn't really work. I tried adding them to the global scss, as well as to the main app one.
Aside from that, I've tried using the browser inspector to check which css selector is actually styling the elements in question, but the attribute selector seems to be random somehow. Current day for example is:
.monthview-current[_ngcontent-ljn-c3]
And after reloading, it is
.monthview-current[_ngcontent-igq-c4]
So clearly that method won't work either... I've also tried adding td.monthview-current, which also didn't work... Those were the suggestions and sample codes I've found from looking up this plugin online and looking around the plugin files. If anyone has any ideas whatsoever I'd be super thankful.
EDIT: I've found a way to change it, but ONLY through the source files for the plugin, which I have to assume is not the right way to do it... There's JSON files, JS files, and I have to manually change all of them.
If the styles are present inside the angular component's file it will not be applied due to view encapsulation. You need to specify the styles in the global stylesheet, and also in most you need to add important to the styles.
To elaborate further,
-src
-assets
-calendar.css (add styles here)
-app
-my-calendar
-my-calendar.page.html
-my-calendar.page.ts
-my-calendar.page.css (and not here)
Some commonly needed customizations: (assets/calendar.css)
Apply styles to the selected date:
.monthview-selected{
font-weight: bold;
background-color: #F1F1F1 !important;
color: #333 !important;
}
Apply styles to the date that has an event:
.monthview-primary-with-event, .calendar-event-inner{
background-color: #1a92d0!important;
}
Disable all the borders in the calendar:
td, th {
border: 0 !important;
}
Final calendar after applying the styles:
HTML
<calendar [eventSource]="eventSource" [calendarMode]="calendar.mode" [currentDate]="calendar.currentDate"
(onCurrentDateChanged)="onCurrentDateChanged($event)" (onRangeChanged)="reloadSource(startTime, endTime)"
(onEventSelected)="onEventSelected($event)" (onTitleChanged)="onViewTitleChanged($event)"
(onTimeSelected)="onTimeSelected($event)" step="30" (showEventDetail)="true" formatDayHeader="EEEEE"
allDayLabel="All Day" startHour="9" endHour="20">
</calendar>
I had the same issue and a solution is related to encapsulation as stated in other answer.
Styling not applying to child component
try update your component:
#Component({
...
encapsulation: ViewEncapsulation.None // <------
})
export class xxComponent{
You can then apply the style based on the child class, eg.
.scss:
.monthview-container {
...;
}
The best way is to use Template Customization given in the plugin.
https://github.com/twinssbc/Ionic2-Calendar/blob/v6/README.md#Template Customization
If that is diffcult in your case. Then add a class to calender tag in html. And get all the child elements in css using Child or descendent combinator. Css Combinator
Although I'm not sure about the reason for this, the solution in my case seems to be using the global stylesheet (without any attribute selector in brackets) instead of the module specific one. It's not ideal, but it works I guess!
With depp
::ng-deep {
.monthview-selected {
background-color: blue !important;
color: white !important;
border-radius: 50%;
}
}

quirks of using Angular Shadow DOM CSS Selector

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.

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