What would be great is if I found a way to change the contents of a <style> block in my Vue component with some Vue variables.
The common question and answer around this involve using in-line styles, or using javascript to access a .style property, but I wish to change slider tracks and pseudo elements much like this question here: .slider::-webkit-slider-thumb needed in javascript
If the official Vue position is that <style>s are static, etc - does anyone else wish they had some system like <style scoped dynamic> where the contents of the style sheet can be controlled by Vue?
I am able to get working results per this answer (How to override scoped styles in Vue components?) with the parent component using a non-scoped which has selectors that overtake the importance of the child component's styles.
As the answer said, using "#app" as the start of the selector does the job - the stumbling block for me was simply adding a non-scoped style block to the parent component.
However it isn't so versatile and I'm looking to learn what "putting styles into JS" means and whether it would also provide the method to override the pseudo-element styles.
Related
A lot of our site is structured as such:
component
component CSS
component template
child component
We often need to style the child component differently based on which parent component it was in. This is annoying, of course, but was manageable when we were using global CSS as we could simply target the specific instance with unique ID and class CSS selectors.
We're trying to now stop using global styles and encapsulating our CSS within each component. The problem with this is that, from what I see, encapsulate angular CSS will scope your CSS so that it only applies to that component...which means it also will not apply to any child components.
One solution appears to be to use ::ng-deep selectors in our component CSS to target the child. This is meeting some resistance, though, as I'm being told this also breaks encapsulation in general and could affect other parts of the site in different components.
This is where I'm confused. Some questions:
Does any style targeted with ::ng-deep become descoped so that it is global application-wide? Or is it simply 'global' within that one parent component?
If it's the former, would it be an OK practice to still use ::ng-deep, but also make sure you are using additional unique css selectors so it only applies to the child component you are targeting?
Or is there a more proper way to add styles to a particular child component instance?
:ng-deep is global. No matter where you put it, it applies to all components. Not just children.
If you use :host :ng-deep, then it will work from that component down (Into the children, grand children etc).
The main issue working with ng-deep is that styles in Angular are lazy loaded. So in some cases, you can be viewing the site perfectly fine, then you view a particular page that uses ng-deep, then you can go back to previous pages that were fine that are now broken because the ng-deep style is applied site wide. e.x. https://tutorialsforangular.com/2020/04/13/the-dangers-of-ng-deep-bleeding/
Generally speaking, if I need to style a child component slightly differently depending on where it's placed, then I create an input variable for the child, make the parent set it, then make it a class somewhere in the child component HTML. The child component can then style that class how it see's fit and you haven't had to break encapsulation.
::ng-deep by itself does make the style global, but when combined with :host may do what you need, but as #eldar alluded to with this link, ::ng-deep is deprecated (and has been for some time).
This question likely has no single direct answer, but hopefully will lead to some best practices or common patterns to use when adapting an existing styles framework to new web component development.
For my case, I have a component <custom-avatar>, and it's all set up properly with self-contained styles and functionality, everything is just peachy.
In certain use cases, the application display needs to stack avatars, just one slightly overtop one other at a diagonal, and the pattern I'm following is using a simple component <custom-composite-avatar>. All this does is wrap the slotted content in a <div> with the correct styling class, but key aspect is retaining the composability for flexible re-use, like so:
<custom-composite-avatar>
<custom-avatar title="first"></custom-avatar>
<custom-avatar title="second"></custom-avatar>
</custom-composite-avatar>
The tricky bit lies in the styles, which are imported from a monorepo that provides the same BEM-ish CSS and component CSS modules to other flavors of the component library like React, Vue, etc. I have the avatar and composite-avatar styles imported just fine, but forcing the intended overlap display is defined with the hierarchical selector .my-composite-avatar.my-composite-avatar--medium .my-avatar {}
So with .my-composite-avatar class applied to the div wrapper within <custom-composite-avatar> and the .my-avatar class applied to the wrapper within the <custom-avatar> and it's own Shadow DOM, that parent/child CSS selector is no good.
I doubt there is a silver bullet for this, but this seems like it will be a rather common scenario as more people migrate to Web Components while using existing styling systems. What approach makes the most sense to ensure that the composite component remains composable, and adaptation of existing selectors pain-free (or at least easy to communicate to other devs)? can this be solved with ::host or ::slotted, or will these cases require significant re-work?
Thanks for reading, your ideas are appreciated!
I would advice to become good friends with CSS properties
because they trickle down into shadowDOMs following CSS selectors.
CSS Custom Properties(variables)
and getPropertyValue
and setProperty if you want to be brutal and make Custom Elements change the outside world.
example
I have an <SVG-ICON> element taking configuration from attributes OR CSS properties
with my favorite lines of code:
let val = this.getAttribute(attr)
||
getComputedStyle(this)
.getPropertyValue("--svg-icon-" + attr)
.replace(/"/g, "")
.trim();
Allows for your standard attribute configuration:
<svg-icon name="configuration" fill="grey"></svg-icon>
But more powerful (simplified example):
<style>
body {
--svg-icon-fill: "grey";
}
svg-icon[selected] {
--svg-icon-fill: "green";
}
</style>
<svg-icon name="messages" selected></svg-icon>
<svg-icon name="configuration"></svg-icon>
CSS = Custom String Scripting
It doesn't often happen, but sometimes the simplest code makes me very happy.
There is no Styling restriction!
These 2 lines allow any String you want in CSS properties:
.replace(/"/g, "")
.trim();
Example
<style>
[name*="globe"] {
--svg-icon-tile: "rect:0,0,24,24,0,fill='blue'";
--svg-icon-stroke: white;
}
</style>
<svg-icon name="feather-icons-globe"></svg-icon>
The --svg-icon-tile has nothing to do with CSS, it is read (and parsed) by the <SVG-ICON> connectedCallback() code to generate a SVG background/tile for all icons named globe.
The double-quotes aren't required, but without them your IDE will complain about invalid CSS.
Have fun coding... you will pull some hairs when you start with calc() in your CSS properties...
But you can take 'CSS' to another level.
PS.
And monitor the future of ConstructAble StyleSheets aka ConstructIble StyleSheets aka Constructed Sheets aka AdoptedStyleSheets:
https://developers.google.com/web/updates/2019/02/constructable-stylesheets
https://chromestatus.com/feature/5394843094220800
iconmeister
So I've got an Angular app that has pretty basic routing, and I'm using flexbox to layout my components. The issue I can't figure out is with code like this:
<div fxLayout="column"
fxFlex
class="layout__right">
<router-outlet></router-outlet>
</div>
The child component that is routed to contains the following in its SCSS:
:host {
#include make-flex-container(column);
flex: 1;
}
That make-flex-container just applies some flexbox related styles, and works fine in many places of the app. What's happening in my case though is when routing to this particular child component I see a style tag applied to the ng-component element created by Angular. What's causing my problem is for some reason the style includes flexbox items that are overriding what I'm putting in :host:
You can see in the screenshot that my :host styles are being applied, but the styles on the ng-component tag are simply overriding them. For the life of me I can't understand why there's a specific style tag added here, so where the content within it would come from. Does anyone know why Angular would put style tags on the HTML generated for router-outlets? When I navigate to other components at this same routing level this style tag isn't present.
I assume this is an issue with my code, but I just can't run down where to look given what I see.
UPDATE
Here's a minimal reproduction of the issue on Stackblitz:
https://so-48451609-routing-flex-issue.stackblitz.io
In that example you can see how the element created next to the router outlet has styles applied to it. The only dependency added there is #angular/flex-layout, so it's gotta be doing something to cause this.
The fxLayout directive applies styles to child elements. It does this on the element itself to not interfere with other styles.
The #angular/flex-layout library's static API has directives that work either on a DOM container or DOM elements. fxLayout is an example of the former, fxFlex is an example of the latter.
See the docs here, note the two sections for elements and containers: https://github.com/angular/flex-layout/wiki/Declarative-API-Overview#api-for-dom-containers
THEY COME FROM VIEW ENCAPSULATION
I believe that these classes are part of view encapsulation that prevents angular elements from being styled from outside and to prevent interference between view components. At least that is what they say in official docs #angular.io.
As suggested #this SO post the '_ngcontent-c0' is naming of the first component within the host.
ANGULAR MATERIAL IS CAUSING TROUBLES
I have found a bunch of issues related to encapsulation at GitHub and they often seem to be related to using Angular Material, which seems to have encapsulation turned off sometimes somehow. Such as described here. Hence I conclude that your implementation of Angular Material might be at fault. For in depth analysis of what the mechanism of this unwanted interaction is, you might probably like to start by reading joh04667's answer to your question since it provides links to the documentation of the particular material you use.
PROPER STYLING
Common solution to those issues is overriding styles with /deep/ selector as described in both above shared links. A number of unofficial articles on styling Angular app is available as well, such as this one.
IDEA
So the idea is to overwrite the styles in your component with /deep/ like this:
:host /deep/ .your_flex_class {
#include make-flex-container(column);
flex: 1;
}
CONSIDER VALID CSS
Since I think that you use Scss, using /deep/ selector should be ok and should be compiled successfully (I use it quite often to style inline SVGs). However, in a fact, this selector seems to be deprecated as a whole bunch of things related to the issue of Shadow DOM. I believe the correct universal approach would be to use 'shadow-piercing descendant combinator':
:host >>> .your_flex_class {
#include make-flex-container(column);
flex: 1;
}
It should does all the same as /deep/ selector. To explain the mechanism, let me cite from official docs for CSS Scoping Module Level 1
When a >>> combinator (or shadow-piercing descendant combinator) is encountered in a selector, replace every element in the selector match list with every element reachable from the original element by traversing any number of child lists or shadow trees.
Basically, it does not care about the wrapper and bites down to your target to style it. So you have even one more option to try ...
Ok, once I created a working example of this I eventually found the culprit and a suitable work-around. What is happening is within my router child component I'm using fxFlex from #angular/flex-layout to flex the element. I'm applying the flexbox container CSS in the :host{} section of the components CSS, but flex-layout doesn't see this in the rendered HTML and applies the style tag to do a flexbox row layout automatically.
Here's an issue on Github with my comments and a suitable workaround of using !important in my :host{} CSS:
https://github.com/angular/flex-layout/issues/496#issuecomment-360870510
I'd like to change the styles of a component outside the scope of my current component. The markup in my app.component.html is as follows:
<app-nav></app-nav>
<div id='content-container'>
<router-outlet></router-outlet>
</div>
I'm looking to change the styles of a class main-nav in the app-nav component from the 'home-component' which is loaded from a route. I use routerLink's in the nav and I've found if I add encapsulation: ViewEncapsulation.None to the home.component.ts I can alter the styles of the the class in the app-nav component however the changes are only applied once and if I go to other routes using the routerLink's I cannot revert the changes (seems like the css is only loaded/applied once - when the component is initialized/comes into view)
Special selectors are designed to access and apply css outside the encapsulation without having to change encapsulation scope. In your case I think :host or :host-context will help.
See https://angular.io/guide/component-styles#special-selectors
Use the :host pseudo-class selector to target styles in the element
that hosts the component (as opposed to targeting elements inside the
component's template).
and
Sometimes it's useful to apply styles based on some condition outside
of a component's view. For example, a CSS theme class could be applied
to the document element, and you want to change how your
component looks based on that.
Use the :host-context() pseudo-class selector, which works just like
the function form of :host(). The :host-context() selector looks for a
CSS class in any ancestor of the component host element, up to the
document root. The :host-context() selector is useful when combined
with another selector.
change the styles of a component outside the scope of my current component
Before I say anything else, this is highly discouraged. Components are supposed to be self-contained entities, which can be reusable across your project, or even other projects. By creating a component that changes something else in the system only through CSS, you're creating a tight coupling which is difficult to see immediately from code, leading to difficult-to-debug bugs and other errors.
If you want to change your app-nav (or main-nav as you refer to it later, I'm not sure if this is my mistake) based on the current route, you should do so by injecting the ActivatedRoute and querying it to determine the specific version of header which you want.
Another option is to simply move the header below the router outlet, so you always direct access to it from the component which rests across the whole page.
the changes are only applied once and if I go to other routes using the routerLink's
This is expected, since the styles are injected into DOM only when you visit the actual component, no need for them to appear before there's need for it. Angular expects that the styles you define for a component and used only on that component. You're breaking this rule of thumb.
I cannot revert the changes (seems like the css is only loaded/applied once - when the component is initialized/comes into view)
Your observation is correct.
Consider the following hierarchy in some Angular 2/4 project:
<parent-cmp [ngClass]="{ 'parent': condition }">
<child-cmp class="child"></child-cmp>
</parent-cmp>
Now in the child component's CSS file I would like to say:
.parent .child {
background-color: red;
}
In this scenario, I'm basing the child's design on parent's logic without knowing what that logic is at child level. But the problem is that, this is not going to work. And that's because, Angular compiles the the child CSS selectors to this:
.parent[_ngcontent-c9] .child[_ngcontent-c9] {
background-color: red;
}
And the parent part of the selector is not going to work anymore. So How can I pull this off? Also please bear in mind that I simplfied this example and the two components are not necessarily one after another (there might be arbitrary number of components in between).
In Angular this is called "view encapsulation" where the JavaScript, CSS styles, and HTML templates are all managed by Angular. There are a lot of advantages to this approach as it allows you to easily tree-shake a project and drop components that are not being used. You not only drop the Javascript code, but all the styles and HTML with it.
When it's turned on the styles are injected into the DOM as embedded styles. Angular keeps track of what styles are required on the document and adds or removes styles as needed. These styles can have strange names at run-time like those in your question.
You need to read up on the https://angular.io/guide/component-styles styles guide to see how to define a :host style. This is the style assigned to a component when view encapsulation is turned on. When using :host you can refer to the parent selector using a :host-context path, and you can also style inside other child components using the ::ng-deep selectors.
Keep in mind. This is all optional. It's turned on by default, but if you don't want to use it. You can turn it off.
You can change the view encapsulation mode when you define your component. To disable this feature just change the encapsulation option to native.
See the guide:
https://angular.io/guide/component-styles#view-encapsulation