Angular change child component style from parent component but not globally - css

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

Related

change width in ::before property using angular and css [duplicate]

Is there any way to control a CSS variable defined at a component's root level using Angular methods? In JavaScript, we have document.documentElement.style.setProperty() when we set at root level.
In angular, can we use ':host' to declare css variable for global access within component? or do we need to use something like '::ng-deep :root'?
The following question also remains unanswered:
Angular: Use Renderer 2 to Add CSS Variable
Yes you can set variables in root scope:
:root {
--main-color: red
}
Yes you can use :host selector to target element in which the component is hosted.
:host {
display: block;
border: 1px solid black;
}
You can also use, :host-context to target any ancestor of the component. The :host-context() selector looks for a CSS class in any ancestor of the component host element, up to the document root.
:host-context(.theme-light) h2 {
background-color: #eef;
}
Note: ::ng-deep or /deep/ or >>> has been deprecated.
Read more about it here: special css selectors in angular
Just an additional information.
It works both inside ':root' as well as ':host'
We can set values to them by:
constructor(private elementRef: ElementRef) { }
then
this.elementRef.nativeElement.style.setProperty('--color', 'red');
The most constructive and modular way to use css vars in components (with viewEncapsulation) is as such:
// global css
:root {
--main-color: red
--alt-color: blue
}
// inside component component css
::ng-deep :root {
--specific-css-var: var(--main-color)
}
:host {
background-color: var(--specific-css-var)
}
:host(.conditional-class) {
--specific-css-var: var(--alt-color)
}
NOTE: despite ::ng-deep being deprecated, it hasn't been replaced yet (and has no replacement), as can be read in several discussion like this
Best thing for each component like a background color with out using ::ng-deep (which sets bg for all children)
import the following
import {ElementRef} from '#angular/core';
declare elementref in constructor
constructor(private elementRef: ElementRef) {}
then call the function ngAfterViewInit()
ngAfterViewInit(){
this.elementRef.nativeElement.ownerDocument.body.style.backgroundColor = ' white';
}
this sets bg to white but you can replace it with a hex color as well, so you can do that with every component

Pierce component style globally

I need to pierce the styles of my component from the global styles.scss file.
Basically, I've used mat-card component wrapped in a custom-component. In
some cases, I want to change styles to the mat-card when a custom-component is preceded by another custom-component
The idea would be:
global-styles.scss
custom-component ~ custom-component::ng-deep {
.mat-card {
background-color: red;
}
}
The host-context seemed like a good idea, I tried it this way
custom-component.scss
// does not work
host-context(~custom-component) { background-color: red; }
I've tried this and some other combinations, but they don't seem to work. How are we supposed to use the >, ~, + selectors to style angular components?.
Cheers
Personally I avoid piercing selectors at all costs. It breaks the entire component model, and tightly couples code.
I would approach this in a slightly different way. I would have my custom-component have an optional Input() embedded = false
Your usage could be as follows:
// top level
<custom-component></custom-component>
// nested level
<custom-component [embedded]="true"></custom-component>
Then use ngClass with the embedded property to trigger the color change.
See docs for more info on ngClass
https://angular.io/api/common/NgClass
Ok not a solution for this, but there's one thing to consider.
If you want to apply styles to your component using the selector your-component, you have to set display: block; property in your component :host. I totally missed this, but it would look like this:
your-component.css
:host {
display: block;
}
your parent component css
your-component ~ your-component {
margin-top: 15px;
}
And it works. My problem was originally related to that.

why css is not applied on p tag When using nested components?

Could you please tell me why css is not applied on p tag ? I have nested components .Now I want to add css in inner component element .but color property not applied why ??
Here is my code
.a p {
color: blue;
}
.a .test{
color: blue;
}
https://stackblitz.com/edit/angular-f99kxh?file=src%2Fapp%2Fhello.css
You can use ::ng-deep to get hold of inner elements.
.a ::ng-deep p {
color: green !important;
}
updated stackblitz
This happens because of angulars css encapsulation, which binds css to the scope of the component it is attached to, rather than allowing its default cascading behaviour.
You can either just create a global css file, which is not component-specific and import it in your index.html or disable it in general.
Components CSS will get applied to HTML view. Hence, you should write css in components css file.
abc.ts
#Component({
selector: 'abc',
template: `<p class="test">abc</p>`,
styleUrls: ['./abc.css']
})
in abc.css add your css.
I hope it will help you!

Control CSS variable from Angular 5

Is there any way to control a CSS variable defined at a component's root level using Angular methods? In JavaScript, we have document.documentElement.style.setProperty() when we set at root level.
In angular, can we use ':host' to declare css variable for global access within component? or do we need to use something like '::ng-deep :root'?
The following question also remains unanswered:
Angular: Use Renderer 2 to Add CSS Variable
Yes you can set variables in root scope:
:root {
--main-color: red
}
Yes you can use :host selector to target element in which the component is hosted.
:host {
display: block;
border: 1px solid black;
}
You can also use, :host-context to target any ancestor of the component. The :host-context() selector looks for a CSS class in any ancestor of the component host element, up to the document root.
:host-context(.theme-light) h2 {
background-color: #eef;
}
Note: ::ng-deep or /deep/ or >>> has been deprecated.
Read more about it here: special css selectors in angular
Just an additional information.
It works both inside ':root' as well as ':host'
We can set values to them by:
constructor(private elementRef: ElementRef) { }
then
this.elementRef.nativeElement.style.setProperty('--color', 'red');
The most constructive and modular way to use css vars in components (with viewEncapsulation) is as such:
// global css
:root {
--main-color: red
--alt-color: blue
}
// inside component component css
::ng-deep :root {
--specific-css-var: var(--main-color)
}
:host {
background-color: var(--specific-css-var)
}
:host(.conditional-class) {
--specific-css-var: var(--alt-color)
}
NOTE: despite ::ng-deep being deprecated, it hasn't been replaced yet (and has no replacement), as can be read in several discussion like this
Best thing for each component like a background color with out using ::ng-deep (which sets bg for all children)
import the following
import {ElementRef} from '#angular/core';
declare elementref in constructor
constructor(private elementRef: ElementRef) {}
then call the function ngAfterViewInit()
ngAfterViewInit(){
this.elementRef.nativeElement.ownerDocument.body.style.backgroundColor = ' white';
}
this sets bg to white but you can replace it with a hex color as well, so you can do that with every component

matTooltipClass not applying css

I am attempting to apply some css changes to mat-tooltip from angular material 2 and found in the documentation a matTooltipClass that can then be selected in the css file to make changes. However, I am not able to get it working.
component.html :
<mat-cell
*matCellDef="let productInfo"
matTooltip="{{productInfo.description}}"
matTooltipClass="tooltip">
{{ productInfo.description}}
</mat-cell>
component.scss:
.tooltip {
background-color: red;
color: blue;
font-size: 20px;
}
You have to use ::ng-deep to override default CSS for material elements:
::ng-deep .tooltip {
background-color: red;
color: blue;
font-size: 20px;
}
In addition to what was stated above,
Here are two methods that worked for me:
-in the Component.scss:
::ng-deep mat-tooltip-component{
& .mat-tooltip{
color: green; // your custom properties here.
}
}
-Globally:
.mat-tooltip{ // making the font size on the mat-tooltip 1.5rem globally
font-size: 1.5rem;
&.exaggerated-tooltip{ // to modify the tooltip create a class like this
font-size: 2.5rem; // and use it like this: *matTooltipClass="exaggerated-tooltip"* in the
color: red; // component in which you are putting the tooltip
}
}
A blog post by Siderite (Styling Angular Material tooltips) provided an answer that worked for me. I am paraphrasing from the most-relevant portion of his post and I am using the matTooltipClass="tooltip" scenario described in the Question above:
[The .tooltip class definition] should either be in the global CSS
file (so it applies to everything) or your component should declare
encapsulation ViewEncapsulation.None. [If the .tooltip class
definition is in the global CSS file], then ensure the declaration of
the class is specific enough: try this:
mat-tooltip-component .mat-tooltip.tooltip {...}.
In my case, I had defined the .tooltip class in the global styles.scss file, and it wasn't working until I followed Siderite's suggestion and defined it like this:
mat-tooltip-component .mat-tooltip.tooltip {
color: blue !important;
}
This approach is avoids using ::ng-deep as suggested in the accepted Answer. Angular documentation states that approach is deprecated. I did find I needed to use !important, which some believe is bad style.
Angular material doc says matTooltipClass supports the same syntax as ngClass.
thus you might try [matTooltipclass]="'tooltip'"
<mat-cell
*matCellDef="let productInfo"
matTooltip="{{productInfo.description}}"
[matTooltipclass]="'tooltip'">
{{ productInfo.description}}
</mat-cell>
From the example provided in the website (https://material.angular.io/components/tooltip/examples):
import {Component, ViewEncapsulation} from '#angular/core';
/**
* #title Tooltip that can have a custom class applied.
*/
#Component({
selector: 'tooltip-custom-class-example',
templateUrl: 'tooltip-custom-class-example.html',
styleUrls: ['tooltip-custom-class-example.css'],
// Need to remove view encapsulation so that the custom tooltip style defined in
// `tooltip-custom-class-example.css` will not be scoped to this component's view.
encapsulation: ViewEncapsulation.None,
})
export class TooltipCustomClassExample {}
If you stick that encapsulation: ViewEncapsulation.None line there for your component where you want to use your custom tooltip class, it ought to work. It's how I fixed the issue on my end.
Seeing that ::ng-deep is depricated now, best seems to be to add encapsulation: ViewEncapsulation.None, in your component decorator under your styleUrls line
Just be careful that all your css classes will be global then, so make sure your classes in that component have unique names if you do not want to overwrite the classes in other components accidentally
I found out there is a div element down the body with class="cdk-overlay-container". I considered its z-index. it was 1000 below the z-index of my modal. I made it the same as my modal's z-index, i.e. 1050. And it is working. I also had to put ::ng-deep before my class in CSS file.
::ng-deep .cdk-overlay-container {
z-index: 1050;
}
In case your project prohibits to use: ::ng-deep, you can use
encapsulation: ViewEncapsulation.None
in the TS file.

Resources