CSS encapsulation with Angular 4 and Materialize - css

Here's an example that introduces both regular Materialize select and Angular Materialize component:
#Component({
selector: 'my-app',
styles: [`
.select-wrapper {
background-color: red;
}
`],
template: `
<div class="row">
<div class="col s6">
<select materialize="material_select">
<option>...</option>
</select>
</div>
<div class="col s6">
<select class="non-angular-materialize">
<option>...</option>
</select>
</div>
</div>
`,
})
export class App {
ngOnInit() {
$('.non-angular-materialize').material_select();
}
}
Styles are not applied.
Although same example works with /deep/, this defies the purpose of components:
:host /deep/ .select-wrapper {
background-color: red;
}
Why does this happen? Is it possible to keep CSS encapsulation and avoid /deep/ when styling the elements of component template? Is this problem specific to Materialize?

ViewEncapsulation.Emulated is used by default.
To emulated css encapsulation angular addes some attributes to all elements within template.
For example, the following template
<div class="col s6">
<select materialize="material_select">
<option>...</option>
</select>
</div>
becames
<div _ngcontent-c0="" class="col s6">
<select _ngcontent-c0="" materialize="material_select">
<option _ngcontent-c0="">...</option>
</select>
</div>
where c0 is unique id of your component.
And besides overwriting template angular also create specific css selectors from styles|styleUrls content.
So
.select-wrapper {
background-color: red;
}
becames
.select-wrapper[_ngcontent-c0] {
background-color: red;
}
Eventually the CSS is not applied because your dynamically added HTML(after running plugin) doesn't have the attributes _ngcontent-c0 applied
Using the "shadow piercing" CSS combinators or having styles outside of styles|styleUrls properties should work around it.

the use of /deep/, >>> and ::ng-deep are with emulated view encapsulation. Emulated is the default and most commonly used view encapsulation.Angular support 3 types of view encapsulation 1.Native: view encapsulation uses browsers native shadow DOM. 2.Emulated(default): emulate the behaviour of shadow DOM by renaming css classes and effectively scope the CSS to the component's view. 3.None: Angular does no view encapsulation. Angular adds the CSS to the global styles.similar to pasting components styles in html
you can set the view encapsulation by adding encapsultion with your component. eg:-
#Component({
selector: 'hero-team',
template: ``,
encapsulation: ViewEncapsulation.None
})

Related

How to stop css bleeding in angular 11.2

I am working on a angular 11.2 project and I see that CSS of a component is getting applied to another component which has the same css selector name. How can I stop this? please help
If you apply the styles in Angular #component, it should be applied to that component scope only.
https://angular.io/guide/component-styles
#Component({
selector: 'app-root',
template: `
<h1>Tour of Heroes</h1>
<app-hero-main [hero]="hero"></app-hero-main>
`,
styles: ['h1 { font-weight: normal; }']
})
export class HeroAppComponent {
/* . . . */
}
Use encapsulation in your component declaretion
#Component({
selector:'app-component',
templateUrl:'app.compionent.html',
encapsulation:ViewEncapsulation.None,
})
My solution was to use ViewEncapsulation.None but to use targeted css. ie using specificity. for example if i had a component structure as below:
<div class="parent">
<div class="child">
<span></span>
</div>
</div>
My css would be :
.parent .child > span { ...some css here}
The reason not to use Emulated is that, some lib components can't be targeted with "None" as ViewEncapsulation. hence we need to set it to None and then follow this approach.

Angular 7: Why class style is not applied to DOM component?

I have a component billing that includes ngb-tabset component of Ng Bootstrap.
ngb-tabset has the following DOM element:
<ngb-tabset _ngcontent-c3="" class="content">
<!-- Tabset links-->
<div class="tab-content">
<div _ngcontent-c3="">
<!-- Content -->
</div>
</div>
</ngb-tabset>
<div class="tab-content"> is dynamically displayed according the selected tab.
In my billing.component.scss, the following snippet doesn't work:
.tab-content {
padding-right: 120px;
}
The CSS code is correctly compiled and, normally, seen by navigator but not effect on the item.
But if I apply it on the item outside the component, the code works correctly.
Any idea about this strange behavior?
Method 1 - Set style classes in the tab content template
View encapsulation isolates the CSS styling of each component, preventing the parent component CSS from affecting the child components. The preferred solution in the present case would be to set the style classes in the tab content template definition. Here is an example:
<ngb-tabset>
<ngb-tab title="Simple">
<ng-template ngbTabContent>
<div class="my-style-1">
<p>Some content</p>
</div>
</ng-template>
</ngb-tab>
...
</ngb-tabset>
.my-style-1 {
padding-right: 120px;
color: magenta;
font-style: italic;
}
See this stackblitz for a demo.
Method 2 - Use the ::ng-deep pseudo-class selector
Another method is to use the ::ng-deep shadow-piercing descendant combinator to style the content of the NgbTabset child component:
::ng-deep .tab-content {
padding-right: 120px;
}
See this stackblitz for a demo.
Method 3 - Turn off view encapsulation
Alternatively, you could turn off the view encapsulation of the parent component:
import { Component, ViewEncapsulation } from '#angular/core';
#Component({
...
encapsulation: ViewEncapsulation.None
})
See this stackblitz for a demo.
Angular uses shadow DOM. That means it keeps DOM logics separate from other elements. Components have scoped styles of their own. They are encapsulated in a way that the styles should not effect globally. So, if you want to change ngb-tabset's style you have to come out of its scope. That's why you need to use ViewEncapsulation.None.
In #Component decorator use ViewEncapsulation.None
#Component({
selector: ....,
encapsulation: ViewEncapsulation.None,
styles: [...])}
And, you can use ng-deep also. But you should not use ng-deep because it will be deprecated. See here- https://angular.io/guide/component-styles#deprecated-deep--and-ng-deep

Angular 4: styleUrls not working correctly

I just came across a strange behaviour in Angular 4. I have the following component:
#Component({
selector: 'user-wallets',
templateUrl: './user-wallets.component.html',
styleUrls: [
'./user-wallets.scss'
]
})
export class UserWalletsComponent implements OnInit, OnDestroy {
<!-- component logic... -->
}
In that template I have the following markup:
<div class="user-wallets">
<h2>
<span jhiTranslate="coinlenderApp.userWallets.home.title">Wallets</span>
</h2>
<jhi-alert></jhi-alert>
<ngb-tabset>
<ngb-tab *ngFor="let userAccountArr of sourceSystemsUserAccounts">
<ng-template ngbTabTitle>
{{ userAccountArr[0].sourceSystem.name }}
</ng-template>
<ng-template ngbTabContent>
<div class="row">
<div class="col-md-12 charts-col">
Charts here
</div>
</div>
</ng-template>
</ngb-tab>
</ngb-tabset>
</div>
Now, in the associated stylesheet (SCSS) I have the following styles:
.charts-col {
background: black;
}
.tab-pane {
float: left;
min-width: 300px;
background: black;
}
The code is working correctly and the tabs are correctly rendered. There's no error in the console.
Now the strange thing is, that the style for .charts-col is correctly applied, but the styles for .tab-pane is not.
.tab-pane is a generated DOM-object out of .
However, when I put exactly the same styles for .tab-pane in my global.scss, then the styles get applied.
Now my question is: Whey does the styles for .charts-col get applied and not the one for .tab-pane (which is generally working in global.scss, but not in the specific styles for the component)?

I have a plunker. When I define my css globally, it works. When I define my css in my component, it fails. What's going on?

With reference to this plunker:
https://plnkr.co/edit/GWsbdDWVvBYNMqyxzlLY?p=preview
I have the same css specified in the styles.css file, and in the src/app.ts file.
If I comment in the css in styles.css and comment out the css in src/app.ts, it works.
styles.css:
/* If these are commented in, and the ones in src/app.ts are
* commented out, the three items are spaced appropriately. */
/***
md-toolbar-row {
justify-content: space-between;
}
md-toolbar {
justify-content: space-between;
}
***/
If I comment out the css in styles.css and comment in the css in src/app.ts, it fails.
src/app.ts:
#Component({
selector: 'my-app',
template: `
<div>
<h2>Hello {{name}}</h2>
<md-toolbar color="primary">
<span><md-icon>mood</md-icon></span>
<span>Yay, Material in Angular 2!</span>
<button md-icon-button>
<md-icon>more_vert</md-icon>
</button>
</md-toolbar>
</div>
`,
// If these are commented in, and the ones in style.css are
// commented out, the three items are scrunched together.
/***/
styles: [
`md-toolbar-row {
justify-content: space-between;
}`,
`md-toolbar {
justify-content: space-between;
}`
]
/***/
})
export class App {
name:string;
constructor() {
this.name = `Angular! v${VERSION.full}`
}
}
I'm having trouble visualizing the difference between defining the css for the whole application, and for the specific component. Can someone tell me what's going on?
=================================
#bryan60 and #Steveland83 seem to indicate that the problem lies somewhere in the view encapsulation. And upon further investigation, it does in a sense.
If you look at the code below, you will see that the styles for md-toolbar and md-toolbar-row have an attribute attached. But the html for md-toolbar and md-toolbar-row does not match. Only md-toolbar has the attribute attached. md-toolbar-row doesn't. I have marked the relevant four lines with >>>>>.
So that's the problem but:
1. Do I report it to the material design people as an error?
2. Is there some workaround I can use today?
<html>
<head>
:
<script src="config.js"></script>
<script>
System.import('app')
.catch(console.error.bind(console));
</script>
<link href="https://rawgit.com/angular/material2-builds/master/prebuilt-themes/indigo-pink.css" rel="stylesheet">
<style>
>>>>> md-toolbar-row[_ngcontent-c0] {
justify-content: space-between;
}
</style>
<style>
>>>>> md-toolbar[_ngcontent-c0] {
justify-content: space-between;
}
</style>
<style>
.mat-toolbar {
display: flex;
: :
.mat-mini-fab,
.mat-raised-button {
outline: solid 1px
}
}
</style>
</head>
<body class="mat-app-background">
<my-app _nghost-c0="" ng-version="4.4.0-RC.0">
<div _ngcontent-c0="">
<h2 _ngcontent-c0="">Hello Angular! v4.4.0-RC.0</h2>
>>>>> <md-toolbar _ngcontent-c0="" class="mat-toolbar mat-primary" color="primary" role="toolbar" ng-reflect-color="primary">
<div class="mat-toolbar-layout">
>>>>> <md-toolbar-row class="mat-toolbar-row">
<span _ngcontent-c0=""><md-icon _ngcontent-c0="" class="mat-icon material-icons" role="img" aria-hidden="true">mood</md-icon></span>
<span _ngcontent-c0="">Yay, Material in Angular 2!</span>
<button _ngcontent-c0="" class="mat-icon-button" md-icon-button=""><span class="mat-button-wrapper">
<md-icon _ngcontent-c0="" class="mat-icon material-icons" role="img" aria-hidden="true">more_vert</md-icon>
</span>
<div class="mat-button-ripple mat-ripple mat-button-ripple-round" md-ripple="" ng-reflect-trigger="[object HTMLButtonElement]" ng-reflect-centered="true" ng-reflect-disabled="false"></div>
<div class="mat-button-focus-overlay"></div>
</button>
</md-toolbar-row>
</div>
</md-toolbar>
</div>
</my-app>
</body>
</html>
One of the Angular features is View Encapsulation which basically means that you can define styles scoped only to a specific component without affecting any other components.
By default styles are scoped only for the component they are referenced in, but you can choose to override that to make them available globally by setting your components encapsulation to None.
E.g.
import { Component, ViewEncapsulation } from '#angular/core';
#Component({
selector: 'component-that-shares-styles',
templateUrl: './component-that-shares-styles.component.html',
styleUrls: ['./component-that-shares-styles.component.scss'],
encapsulation: ViewEncapsulation.None // <-- Set encapsulation here
})
*Note that you will need to import ViewEncapsulation from #angular/core
Okay, with help from #Steveland83 and #bryon60, I came to a definite answer. The Material Design people are aware of this problem. They have made a writeup.
https://github.com/angular/material2/blob/master/guides/customizing-component-styles.md
Here's their summary:
Styling other components
If your component has view encapsulation turned on (default), your component styles will only
affect the top level children in your template. HTML elements belonging to child components cannot
be targeted by your component styles unless you do one of the following:
Add the overriding style to you global stylesheet. Scope the selectors so that it only affects
the specific elements you need it to.
Turn view encapsulation off on your component. If you do this, be sure to scope your styles
appropriately, or else you may end up incidentally targeting other components elswhere in your
application.
Use a deprecated shadow-piercing descendant combinator to force styles to apply to all the child
elements. Read more about this deprecated solution in the
Angular documentation.
I don't want to use global css, or a deprecated solution. I guess I will style with classes, and not elements. If someone has a better idea, tell me!

Cannot Understand the Use of :host in components in Angular2

What i have understood of host is that if i have a child component inside a parent component and we want to style a child component from the parent component we can use :host .
and :host-context for vice-versa.
Please let me know if this is the right use of host .
https://angular.io/docs/ts/latest/guide/component-styles.html
When i try to do the same in my App it dosent work
App component template
<div class ="top">
<h1>
Home Component
</h1>
<hr>
<app-ngrx></app-ngrx>
<router-outlet></router-outlet>
<div>
ngrx component template
<h3 class="mine">NGRX</h3>
<button (click)="increment()">Increment</button>
<div>Current Count: {{ counter | async }}</div>
<button (click)="decrement()">Decrement</button>
<button (click)="reset()">Reset Counter</button>
App component CSS
:host(.mine){
color:red;
}
This do not Seem to work Please help i am not able to understand.
I looked at this question But just not able to figure out
Angular 2: How to style host element of the component?
Updated after #Gunter Answer
In my app-ngrx template i have added
<h3 class = "mine">NGRX</h3>
<button (click)="increment()">Increment</button>
<div>Current Count: {{ counter | async }}</div>
<button (click)="decrement()">Decrement</button>
<button (click)="reset()">Reset Counter</button>
and in the app-ngrx css file i have added
:host(.mine){
color:red;
}
But even without adding mine in app component like
<app-ngrx></app-ngrx>
The h3 is red where as i feel it should be red when <app-ngrx class = "mine"></app-ngrx>
What i have understood of host is that if i have a child component
inside a parent component and we want to style a child component from
the parent component we can use :host . and :host-context for
vice-versa
No, this is not what it used for.
:host selector comes from shadow DOM spec.
...This scoped subtree is called a shadow tree. The element it's
attached to is its shadow host.
In angular world, a component's template is a shadow tree. The component's element is a shadow host. So when you're defining styles for :host selector, the styles are applied to the component's element.
:host
In your example, if you defined styles in my-app component, the styles will be applied to <my-app> DOM element. This particular configuration:
:host(.mine){
color:red;
}
Will be applied to the host element that has .mine class:
<my-app class="active">
If you defined styles in app-ngrx component, the styles will be applied to <app-ngrx> DOM element, NOT <my-app>. This particular configuration:
:host(.mine){
color:red;
}
Will be applied to the host element that has .mine class:
<app-ngrx class="active">
:host-context
Now, :host-context is also applied to the host element, but the function (parenthesis) takes a selector that is checked not against the host element itself, but against all ancestors up to document root. If such element is found, the styles are applied.
For example, this selector
:host(.mine){
color:red;
}
matches such structure:
<my-app class="mine">
whereas, this selector:
:host-context(.mine){
color:red;
}
matches this structure:
<div class="mine">
...
<my-app>
This is useful, if you want to apply styles to components view (shadow root) conditionally. This makes h2 always bold:
h2 {
font-weight: bold;
}
whereas this
:host-context(.make-inner-components-bold) h2 {
font-weight: bold;
}
makes them bold only if your component is inside an element with class .make-inner-components-bold.
:host { ... } selects the component itself
:host(.mine) { ... } selects the component itself when it has class="mine" set
:host-context(.mine) { ... } selects the component itself when one of its ancestors has class="mine" set
See also https://angular.io/docs/ts/latest/guide/component-styles.html
#Component({
selector: 'h3',
styles: [':host(.mine) { color: red; }],
template: '<ng-content></ng-content>'})
class MyH3Component{}
<h3 class="mine">this is red</h3>
<h3>this is black</h3>
or with :host-context
#Component({
selector: 'h3',
styles: [':host-context(.mine) { color: red; }],
template: '<ng-content></ng-content>'})
class MyH3Component{}
<body class="mine">
<my-app><my-app>
<body>
AppComponent
template: '<h3>this is red</h3>'
or with class="mine" set
<body>
<my-app><my-app>
<body>
AppComponent
template: '<h3>this is black</h3>'
update
If you want to style the content of a child component (instead of the child component itself) you can use /deep/
:host child /deep/ h3 {
color: red;
}
update 2
::slotted is now supported by all new browsers and can be used with `ViewEncapsulation.ShadowDom
https://developer.mozilla.org/en-US/docs/Web/CSS/::slotted

Resources