Hey there I have multiple components within each-other.
I want them all to have the CSS variable --border-radius and pass the value down to the children component's variable.
I have 4 components deep but for this example I'll simplify it to 2 components to illustrate my problem.
Component A
:host{
--border-radius: 50%;
border-radius: var(--border-radius);
component-b {
--border-radius: var(--border-radius);
}
}
Component B
:host{
--border-radius: 0;
border-radius: var(--border-radius);
}
When I inspect component B in the browser, I see it's border-radius is set to "var(--border-radius)" instead of the expected "50%".
Is there a way to accomplish this?
EDIT:
Note Component A & Component B are both stand-alone components that should be able to take the CSS property --border-radius in. The issue is with passing the variable property down to the next component that has the same variable name.
Assuming you wanted to create a CSS variable that stored the border radius of your elements, you can use the :root selector to create a global variable, example :
:root { --border-radius: 50%; }
Okay so after thinking about how CSS works for awhile with the fact that it cascades the styles down... I came to the conclusion that I was thinking about the variables incorrectly.
The way it works is:
Component A defines --border-radius as 10px;
Component B does not define it, but instead uses it if it exists, otherwise uses a default value. By not defining it at the beginning of B, it will automatically cascade down from A.
Component A
:host {
--border-radius: 10px;
border-radius: var(--border-radius);
}
Component B
:host{
border-radius: var(--border-radius, 0);
}
In that example, B will default to 0, but accept A's 10px or any other value you pass directly into B.
Related
My Web Component uses CSS variables.
These variables need default values.
They are used in many files, so I want to provide the defaults once, and only once.
This first attempt makes the text black. Why?
What is the correct way to provide the defaults once?
.a {
--my-variable: red;
}
.b {
--my-variable: var(--my-variable, blue);
}
<div class="a">
<div class="b">
<span style="color: var(--my-variable);">text</span>
</div>
</div>
To complement the previous answers, there might be a case where you don't want to declare your variables in the global :root scope. For example, when you're creating a re-usable component, you want to declare its styles locally, without depending on the global project styles. Especially if you're building a library for other developers.
In that case, the solution is to expose one variable name to the "outer world", and use a different variable name inside of the component. The component container should just map the optional external variable to the inner variable, and set its default value:
.my-component-container {
/* map optional "external" variables to required "internal" variables */
--my-variable-inner: var(--my-variable, blue);
}
.my-component-container .my-nested-element {
color: var(--my-variable-inner);
}
.my-component-container .my-other-nested-element {
border-color: var(--my-variable-inner);
}
This way you can ensure that --my-variable-inner is always defined in the component, and make it optional for the external consumers to define --my-variable.
The downside is that you need to remember two variable names instead of one. But here you can think of some project-wide convention, e.g. add --inner or some other suffix to each variable like that.
Declare default values in :root, then override in selectors.
:root {
--primary-color: red;
}
* {
color: var(--primary-color);
border: 1px solid var(--primary-color);
padding: 0.25rem;
margin: 0;
}
div {
--primary-color: green;
}
p {
--primary-color: blue;
}
<div>HI!</div>
…
<p>Bye!</p>
This first attempt makes the text black. Why?
Because this --my-variable: var(--my-variable, blue); is invalid as you are trying to express the same variable with itself which is not allowed so the browser will simply ignore it. Then later when using color: var(--my-variable); the color will fallback to the initial value which is black.
The correct way is to simply define the variable on an upper level and it will get inherited by all the element (like the solution provided by #kornieff)
From the specification:
Custom properties are left almost entirely unevaluated, except that they allow and evaluate the var() function in their value. This can create cyclic dependencies where a custom property uses a var() referring to itself, or two or more custom properties each attempt to refer to each other.
For each element, create a directed dependency graph, containing nodes for each custom property. If the value of a custom property prop contains a var() function referring to the property var (including in the fallback argument of var()), add an edge between prop and the var. Edges are possible from a custom property to itself. If there is a cycle in the dependency graph, all the custom properties in the cycle must compute to their initial value (which is a guaranteed-invalid value).
My Web Component uses CSS variables.
These variables need default values.
They are used in many files, so I want to provide the defaults once, and only once.
This first attempt makes the text black. Why?
What is the correct way to provide the defaults once?
.a {
--my-variable: red;
}
.b {
--my-variable: var(--my-variable, blue);
}
<div class="a">
<div class="b">
<span style="color: var(--my-variable);">text</span>
</div>
</div>
To complement the previous answers, there might be a case where you don't want to declare your variables in the global :root scope. For example, when you're creating a re-usable component, you want to declare its styles locally, without depending on the global project styles. Especially if you're building a library for other developers.
In that case, the solution is to expose one variable name to the "outer world", and use a different variable name inside of the component. The component container should just map the optional external variable to the inner variable, and set its default value:
.my-component-container {
/* map optional "external" variables to required "internal" variables */
--my-variable-inner: var(--my-variable, blue);
}
.my-component-container .my-nested-element {
color: var(--my-variable-inner);
}
.my-component-container .my-other-nested-element {
border-color: var(--my-variable-inner);
}
This way you can ensure that --my-variable-inner is always defined in the component, and make it optional for the external consumers to define --my-variable.
The downside is that you need to remember two variable names instead of one. But here you can think of some project-wide convention, e.g. add --inner or some other suffix to each variable like that.
Declare default values in :root, then override in selectors.
:root {
--primary-color: red;
}
* {
color: var(--primary-color);
border: 1px solid var(--primary-color);
padding: 0.25rem;
margin: 0;
}
div {
--primary-color: green;
}
p {
--primary-color: blue;
}
<div>HI!</div>
…
<p>Bye!</p>
This first attempt makes the text black. Why?
Because this --my-variable: var(--my-variable, blue); is invalid as you are trying to express the same variable with itself which is not allowed so the browser will simply ignore it. Then later when using color: var(--my-variable); the color will fallback to the initial value which is black.
The correct way is to simply define the variable on an upper level and it will get inherited by all the element (like the solution provided by #kornieff)
From the specification:
Custom properties are left almost entirely unevaluated, except that they allow and evaluate the var() function in their value. This can create cyclic dependencies where a custom property uses a var() referring to itself, or two or more custom properties each attempt to refer to each other.
For each element, create a directed dependency graph, containing nodes for each custom property. If the value of a custom property prop contains a var() function referring to the property var (including in the fallback argument of var()), add an edge between prop and the var. Edges are possible from a custom property to itself. If there is a cycle in the dependency graph, all the custom properties in the cycle must compute to their initial value (which is a guaranteed-invalid value).
I have created a shared component(<nextgen-table></nextgen-table>) based on Mat-table (Angular Material). While using this component inside a project, discover that I need to change the behavior(width) of the table columns.
I have exported nextgen-table in my other components (let's say X, Y ) where nextgen-table is, of course, a child component.
To change the width of specific columns of the mat-table I have to use something like this:
mat-cell:nth-child(1),
mat-header-cell:nth-child(1) {
flex: 0 0 40%;
text-align: left;
}
mat-cell:nth-child(2),
mat-header-cell:nth-child(2) {
flex: 0 0 20%;
}
The above CSS code I was implementing in the X.component.css and it was not working because of encapsulation I guess.
After a little bit of search, I have found the solution which worked correctly just by adding the encapsulation: ViewEncapsulation.None in the Component decorator of x.component.ts. After this solution, I was navigating from the component X to the Component Y in which I didn't implement the above CSS code. But the component Y had the first two columns as I wanted only for component X but somehow component Y had also which I didn't want for the component Y.
So my question is how can I update the style of nextgen-table from the parent component which only applies for the parent component and not in the other components.
I have also tried to use
:host(mat-cell:nth-child(1)){
flex: 0 0 40%;
text-align: left;
}
:host(mat-header-cell:nth-child(1)) {
flex: 0 0 40%;
text-align: left;
}
but nothing happened/changed.
Thanks in advance for the help
You can use the ::ng-deep pseudo class, to specifically target child elements without changing the view encapsultation for the whole component (which would mean that all its rules would leak).
Note: ::ng-deep has been marked as deprecated for since a few major versions now, but they will not remove suppoprt until they have a workaround.
parentX.html
<div class="compContainer">
<nextgen-table></nextgen-table>
</div>
parentX.scss
::ng-deep .compContainer nextgen-table
{
mat-cell:nth-child(1),
mat-header-cell:nth-child(1) {
flex: 0 0 40%;
text-align: left;
}
mat-cell:nth-child(2),
mat-header-cell:nth-child(2) {
flex: 0 0 20%;
}
}
You could also add your css rules to the global style.scss file.
//Rules for parent X
app-parent-componentX .compContainer nextgen-table
{
mat-cell...
}
//Rules for a parent Y
app-parent-componentY .compContainer nextgen-table
{
mat-cell...
}
All you need to do is use both :host and ::ng-deep pseudo-class selectors in your X or Y component.
Here is the working demo.
And here is the quick explanation.
styles which are written for the <nextgen-table> inside let say nextgen-table.component.css are get encapsulated by angular by adding a specific attributes for each style. i.e if you have written something like,
.mat-header-cell{
background-color: #ff0000;
}
then it becomes something like,
.mat-header-cell[_ngcontent-c29]
background-color: #ff0000;
}
So all we need to do is to override this style inside our component X or component Y.
We have ::ng-deep pseudo-selector which will prevent angular from encapsulating out component's css.
But using ::ng-deep will leak our css on to parent components as well. So to prevent that we need to encapsulate out ::ng-deep style. to do that we can use :host pseudo-selector.
so if we write following css inside component X,
:host ::ng-deep .x-table .mat-header-cell{
background-color: lightblue;
}
then it will become something like,
[_nghost-c82] .x-table .mat-header-cell {
background-color: lightblue;
}
now this above css selection has higher precedence than the style written in the table component .mat-header-cell[_ngcontent-c29].
That's how we can override child component's style inside any parent component.
I hope this will help.
Update:
As you can see in Angular's official docs that ::ng-deep is deprecated.
The shadow-piercing descendant combinator is deprecated and support is
being removed from major browsers and tools. As such we plan to drop
support in Angular (for all 3 of /deep/, >>> and ::ng-deep). Until
then ::ng-deep should be preferred for a broader compatibility with
the tools.
So if you don't want to be dependent on ::ng-deep than,
You can use ViewEncapsulation.None in your <nextgen-table> table component which you have already tried. Demo here
And to prevent the style from bleeding into other components you can scope the table's style by adding the selector in front of all the styles like this.
nextgen-table .mat-header-cell{
background-color: #ff0000;
}
and then you do the same for your X component.
Disable view encapsulation using ViewEncapsulation.None
Then override styles on table component by writing styles that have higher specificity than the table's actual style.
disable encapsulation in side your X component,
#Component({
selector: "app-x",
styleUrls: ["x.component.css"],
templateUrl: "x.component.html",
encapsulation: ViewEncapsulation.None
})
export class XComponent {
}
then override the table's component style in x.compoent.css
app-x nextgen-table .mat-header-cell{
background-color: lightblue;
}
If you don't want to disable the view encapsulation then you can write styles directly into global stylesheet styles.css.
Just remember that It's all about overriding and scoping your styles.
The only reliable way i've ever found is to use ::ng-deep
Anything else seems to be "hit or miss" intermittently
My Web Component uses CSS variables.
These variables need default values.
They are used in many files, so I want to provide the defaults once, and only once.
This first attempt makes the text black. Why?
What is the correct way to provide the defaults once?
.a {
--my-variable: red;
}
.b {
--my-variable: var(--my-variable, blue);
}
<div class="a">
<div class="b">
<span style="color: var(--my-variable);">text</span>
</div>
</div>
To complement the previous answers, there might be a case where you don't want to declare your variables in the global :root scope. For example, when you're creating a re-usable component, you want to declare its styles locally, without depending on the global project styles. Especially if you're building a library for other developers.
In that case, the solution is to expose one variable name to the "outer world", and use a different variable name inside of the component. The component container should just map the optional external variable to the inner variable, and set its default value:
.my-component-container {
/* map optional "external" variables to required "internal" variables */
--my-variable-inner: var(--my-variable, blue);
}
.my-component-container .my-nested-element {
color: var(--my-variable-inner);
}
.my-component-container .my-other-nested-element {
border-color: var(--my-variable-inner);
}
This way you can ensure that --my-variable-inner is always defined in the component, and make it optional for the external consumers to define --my-variable.
The downside is that you need to remember two variable names instead of one. But here you can think of some project-wide convention, e.g. add --inner or some other suffix to each variable like that.
Declare default values in :root, then override in selectors.
:root {
--primary-color: red;
}
* {
color: var(--primary-color);
border: 1px solid var(--primary-color);
padding: 0.25rem;
margin: 0;
}
div {
--primary-color: green;
}
p {
--primary-color: blue;
}
<div>HI!</div>
…
<p>Bye!</p>
This first attempt makes the text black. Why?
Because this --my-variable: var(--my-variable, blue); is invalid as you are trying to express the same variable with itself which is not allowed so the browser will simply ignore it. Then later when using color: var(--my-variable); the color will fallback to the initial value which is black.
The correct way is to simply define the variable on an upper level and it will get inherited by all the element (like the solution provided by #kornieff)
From the specification:
Custom properties are left almost entirely unevaluated, except that they allow and evaluate the var() function in their value. This can create cyclic dependencies where a custom property uses a var() referring to itself, or two or more custom properties each attempt to refer to each other.
For each element, create a directed dependency graph, containing nodes for each custom property. If the value of a custom property prop contains a var() function referring to the property var (including in the fallback argument of var()), add an edge between prop and the var. Edges are possible from a custom property to itself. If there is a cycle in the dependency graph, all the custom properties in the cycle must compute to their initial value (which is a guaranteed-invalid value).
I have a button class that sets up padding etc for an element, followed by a class that defines a background colour.
.button {
padding: 0.5em 1em;
text-transform: uppercase;
color: #fff;
&.green {
background:#green; //declared previously
}
// ... more colours
}
Is it possible to declare the #green variable as the class name? This would save me having to copy/paste the &.green block for each colour I am wanting to use.
I've not been able to find anything the docs regarding this sort of selector, but something along the lines of:
&.(green|blue|red) {
background: #{$1};
}
which would generate the following:
.button.green{background:#00ff00;}
.button.blue{background:#0000ff;}
.button.red{background:#ff0000;}
You could achieve this by having a variable with the required list of colors, a loop to create the required rules and selector interpolation like shown below.
#colors: "green","blue","orange","red","yellow"; // the list of colors required
button {
padding: 0.5em 1em;
text-transform: uppercase;
.loop-colors(#index) when (#index > 0){ // loop to generate rules for each color
.loop-colors(#index - 1); // call for the next iteration
#color: e(extract(#colors, #index)); // pick the color value from the list one by one based on current index
&.#{color} {
background:#color;
}
}
.loop-colors(length(#colors));
}
Codepen Demo
Note: As mentioned in comments, LESS PHP is quite outdated and hence some of the new features offered by LESS (with respect to loops) are not supported by it. It could be overcome by doing the work-around mentioned in this answer.
You could also adopt an approach similar to the one mentioned by seven-phases-max in this answer (2nd option). That one achieves a similar effect without using loops.