Host Styles Applied to Component in Angular2 - css

I am currently in the process of creating a custom form component for my Angular component and am running into some styling issues.
I'd like for my input to be able to take variable widths and for them to be controlled by the host component.
For example: <my-input class="input"> would have a corresponding class as such:
.input {
width: 250px;
}
For some reason, if I do this, the rules are just ignored and not applied to my component.
I've seen others wrap their components in what seems like unnecessary divs and then styling those parent divs so that the content inside fits. I don't want to write wrappers just for styling purposes when a CSS rule would suffice.
Would doing this involve using :host ?
Also, how about, host-context(.input) and then applying the classes inside of the consumers CSS ?

I would advice against using ::ng-deep since the Angular team is planning on deprecating it.
If you want to style something within your component from whatever component is hosting yours I suggest you create a host-binding/input and then use that value to bind to your component using the style-directive.

Related

Using existing CSS selectors to apply styles across the Shadow DOM to custom elements

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

Vue - change contents in scoped <style>?

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.

Where is this style attribute on my Angular router-outlet element coming from?

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

Angular2 change parent styles outside of component

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.

How to use parent component's class in a child css selector

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

Resources