Is there a way to allow ngClass to be set on a parent element, but redirect it so the classes are actually applied to a child element?
<!-- Parent Component -->
<app-progress-bar [ngClass]="someObject"><!-- Specify [ngClass] here… -->
</app-progress-bar>
<!-- Child Component -->
<div class="progress-bar" [ngClass]="ngClass"><!-- …but it actually applies here -->
<progress [max]="max" [attr.value]="value">
</progress>
</div>
Obviously this could be done by using a custom #Input on the parent component, then feeding that input into the [ngClass] declaration in the child. Can it be done directly without an intermediary property?
You could achieve this with the ::ng-deep selector. The css styles will be applied on the app-progress-bar component but the styles will be activated on the child.
:host {
.some-style-evaluated-by-ng-class {
::ng-deep .progress-bar {
background: red;
}
}
}
ng-deep
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.
Related
My problem does not appear to be solvable by using traditional ways of conditional styling, like [ngStyle] or [ngClass]. I want to conditionally define a CSS-selector using :host ::ng-deep, for example:
<style *ngIf='preventXScroll'>
:host ::ng-deep .p-datatable-wrapper {overflow-x: hidden !important;}
</style>
But doing it this way always applies the style, regardless of the actual state of preventXScroll. Any ideas?
Actually, the problem can be solved via [ngClass].
Template:
<div class='outer-wrapper' [ngClass]='{"prevent-x-scroll": preventXScroll}'>
<p-table>
...
</p-table>
</div>
Stylesheet:
:host ::ng-deep .prevent-x-scroll .p-datatable-wrapper {
overflow-x: hidden !important;
}
This way the style is only applied to p-datatable-wrapper (within p-table child component) while it is contained in prevent-x-scroll.
With a normal React/Vue component I normally create a custom CSS class and use that to style the component like so:
<div class="foo">
...
</div>
.foo {
}
With web components, there is the additional option to use the :host psuedo-selector. I understand that it can be used to apply styles to the web component HTML element itself.
<div class="foo">
...
</div>
.foo {
}
:host {
}
When should I use :host versus a custom CSS class like .foo?
An important part you do not mention, is that React (and other Frameworks), in the build step, rewrites all your classNames, to create "unique" class names.
React does not scope CSS like shadowDOM does.
Below answer is for a Web Component with shadowDOM;
Note that shadowDOM can be attached to regular DOM Elements as well. Same answer applies.
:host refers to your ... host...Web Component DOM element: <my-element>
Bluntly said, you could compare it to html,head,body in global CSS, it is the container element
The CSS inside does not (have to) know the element name my-element
classes (or any CSS selector you know) style Web Component content
OR, if you are not using shadowDOM, they style your whole document,
because unlike Frameworks, classNames are not changed, to be "unique", in the Build step.
And do learn <slot> behaviour:
::slotted CSS selector for nested children in shadowDOM slot
<style>
.foo {
border:1px solid red; /* CSS not applied to elements in shadowDOM */
font: 30px Arial; /* for UX consistancy, font styles DO style shadowDOM */
}
</style>
<span class="foo">
<my-element>Hello</my-element>
Web Components
<my-element pink>World</my-element>
</span>
<template id="MY-ELEMENT">
<style>
:host {
display:inline-block;
background:lightgreen;
}
:host([pink]) { background:hotpink }
.foo { font-weight:bold; /* does NOT style anything outside this shadowDOM */ }
</style>
<slot class="foo"></slot>
</template>
<script>
customElements.define('my-element', class extends HTMLElement {
constructor() {
super().attachShadow({mode: 'open'})
.append(document.getElementById(this.nodeName).content.cloneNode(true));
}
});
</script>
I have a component nested within a component with a bunch of HTML. In the parent component, I am trying to have the CSS written there apply to the nested component.
Example:
/* this is within the parent css */
button{
color: red;
}
But then in the child the component's button is not red.
I am linking the components like so:
<!-- this is within the HTML of the parent -->
<div>
<nested-component></nested-component>
</div>
You can prepend :host ::ng-deep to achieve this.
:host ::ng-deep button {
color: red;
}
As I understand :host-context is used to apply styles based on selector of parent.
Lets consider a rule as follows:
:host-context(.red-theme) { background-color: red; }
same can be written using :host selector as folows:
.red-theme :host { background-color: red; }
Then whh is host-context explicitly required?
Use :host if you want to style the component custom HTML element itself.
Where as :host-context is used when you also want to have a component apply a style taking into account context in which component is rendered.
So for example, you could do: (with host-context)
<div class="red-theme">
<app-component></app-component>
</div>
where app-component
<button class="btn btn-theme">Button</button>
and the component style is defined:
:host-context(.red-theme) .btn-theme {
background: red;
}
Useful if you want to have multiple alternative themes on your web application.
:host and :host-context are not Angular features, but CSS features.
Within the demo provided at stackblitz.com/edit/angular-z3xxtu, the Angular components are merged into the DOM and allow plain old CSS selector structure like .parent :host .child { color: red; }.
This changes when using Webcomponents and ShadowDOM.
The ShadowDOM acts as a barrier and encapsulates the contained markup and style, so the .parent cannot style the things on the :host and vice versa. Note cannot is not totally true, see ::part and CSS Custom Properties.
With a structure like <parent> <my-component> ShadowDOM <child> the above CSS rule no longer works and :host-context(.parent) (which could be read as .parent :host) is needed. Sadly as of 2023-01-10 this doesn't work in Firefox and Safari.
In the above demo, :host-context(.red) { color: red; } and .red :host { color: red; } produce the same output because there's no ShadowDOM involved. On top of that, Angular transforms the invalid .test :host in the CSS to .test [_nghost-some-id] which is a valid CSS selector.
I am using primeNg. My .html and .scss file like below. But I can't give any styleClass to p-panel. What is wrong in my code?
.html file
<p-panel header="Student Info" styleClass="test">
<div class="ui-g">
<div class="ui-g-12 ui-md-3">
<div class="ui-g-12">
<b>Student</b>
</div>
</div>
</p-panel>
.scss file
.test {
margin-top: 50px;
}
To apply a CSS style to a child component, use ::ng-deep. See this stackblitz for a demo.
:host ::ng-deep .test {
margin-top: 50px;
}
According to Angular documentation:
Component styles normally apply only to the HTML in the component's
own template.
Use the /deep/ shadow-piercing descendant combinator to force a style
down through the child component tree into all the child component
views. The /deep/ combinator works to any depth of nested components,
and it applies to both the view children and content children of the
component.
Use /deep/, >>> and ::ng-deep only with emulated view encapsulation.
Emulated is the default and most commonly used view encapsulation.
...
::ng-deep should be preferred for a broader compatibility with the
tools.
An alternative solution is to set the view encapsulation of the component to ViewEncapsulation.None.
Another alternative is to define the style globally in styles.css, as shown for the second panel in the stackblitz.
One more solution is to use the parent element of <p-panel> element. So for,
<div class="panel-container">
<p-panel>
..
</p-panel>
</div>
We could simply have a class as:
.panel-container div.ui-panel-titlebar {
..
}
and/or
.panel-container div.ui-panel-content {
..
}
BTW to fix #HasanOzdemir's problem we could simply add a little padding to the top of parent element ;-)