angular
angular copied to clipboard
Clarify information about deprecation of :ng-deep and recommend replacement
The documentation at https://angular.io/guide/component-styles states this about :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).
As I understand, Angular was using the browser support for the piercing selectors /deep/
and >>>
.
But :ng-deep
is surely not supported by any browser.
And this is what I find confusing in the documentation. I don't understand what browser support has anything to do with :ng-deep
.
The way I understand it, :ng-deep
prevents Angular from appending the attribute selectors to all selector following it.
So, this CSS in a component:
:host ::ng-deep .my-button .ui-button-text {
font-weight: bold;
}
will be transformed to:
[_nghost-c12] .my-button .ui-button-text {
font-weight: bold;
}
Without :ng-deep
, this will be produced instead:
[_nghost-c12] .my-button[_nghost-c12] .ui-button-text[_nghost-c12] {
font-weight: bold;
}
(obviously breaking my intention).
I find 2 problems with the documentation, as it stands now:
- I don't see any relationship between lack of browser support for piercing selectors and deprecating
:ng-deep
- I don't see any reason to deprecate
:ng-deep
(since it just tells Angular: "keep your attribute selectors away from my styles")
I propose the following:
- if I missed anything, and there is a valid reason why
:ng-deep
should be deprecated, the documentation should be changed to explain why - if my reasoning above is correct, remove the section about deprecating
:ng-deep
, keeping the deprecation notice only for the piercing selectors/deep/
and>>>
.
Duplicate of https://github.com/angular/angular/issues/17867.
:ng-deep
is only used to provide deprecating stage, allowing user to drop this usage with a forgiving time period. It's never a designed new feature of Angular.
:ng-deep
is indeed not supported by browser, that's why it shouldn't be supported by Angular in the first place.
:ng-deep is indeed not supported by browser, that's why it shouldn't be supported by Angular in the first place.
I fail to see the logic in this.
:ng-deep
doesn't need browser support. It only needs Angular pre-processing support, and we already have this.
@cvmocanu The ViewEncapsulation.Emulated
is an exact shim of Shadow DOM behavior, there won't be any so-called Angular rule for that.
I guess I need to do some reading on the Shadow DOM.
It looks to me that the people on the Shadow DOM standards commission just deprecated a useful feature, without providing an alternative :(
@trotyl #17867 had no resolution.
Angular seems confused by whether it wants to offer Angular features or be a compatibility layer for W3C ~specs~ drafts (which are a constantly moving target, making Angular an unstable platform).
Looks like the resolution for https://github.com/angular/angular/issues/17867 (thanks for the link, @trotyl ) is to watch https://github.com/angular/angular/issues/23636. This seems to be an open question of what the Angular platform supports, and that issue contains the discussion.
I believe the documentation is, for now, correct and complete:
"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."
When we have more information about the replacement options, we will publish them.
@IgorMinar When you get a chance, is my assessment above correct? Is there anything we can or should add now to the documentation here: https://angular.io/guide/component-styles#deprecated-deep--and-ng-deep
Thank you.
Has there been any resolution to this? The documentation just leads users to search for an alternative to :ng-deep which in turn leads them to a handful of GitHub issues like this one.
Most of my Custom Elements comprise nested Angular Components. Within a Custom Element, I want to be able to apply styles to the nested components. I understand that we don't want styles to bleed into the Custom Element from the page, but from within the Custom Element, I should be able to style the components that I'm using. Ideally, the Custom Element [Web Component] would be shielded (or sandboxed), but the ability to apply styles to nested components within the Custom Element is a really useful feature. Is it possible for the Web Component to use Shadow DOM, but inside the Shadow DOM Angular still provides an emulated environment within which ::ng-deep would work. No bleeding in or out!
@Fokey the clear choice replacement (or at least partial replacement) is the standardized ::slotted().
See #27614 but if you're looking for any participation/response from the Angular team you will be disappointed :/
While I cannot comment on the "best practices" aspect, I can share how we are currently using this feature. We use several third party UI components that come bundled with their own CSS. Some of these components offer limited customization options (e.g. no support for templates and no ability to set CSS classes), so our options include: 1) apply styles globally, 2) use ng-deep, 3) edit their source code, or 4) find a different library. We opted for option 2, as it enables us to style each component that we don't have easy control over. Granted, it's not "best practice" but in the industry we often have to deal with / work around others' code.
@neil-119 same here.
- is most often not possible
- not a solution
- little-bit old-school isnt it?
- elegant solution - still didnt get a reason for deprecation in browsers..
@neil-119 you are missing the best option...
Rather than using global styles (and having a huge src/app/styles.scss
file), given you want to use such a library component in a component:
- define a class on the component's container element
- set the component to
encapsulation: ViewEncapsulation.None
- wrap all styles in the component (SCSS) with the class of the component's container element
- target any selectors that you want in the library component
This allows you to style that library component differently in different parts of your app rather than having a single global style.
Note that in some, concise, isolated cases, the single global style is the best approach.
This can be used to eliminate your dependence upon ::ng-deep
.
@Splaktar is there any reason not to use component selector as CSS selector then?
something-list.component.ts -> "app-something-list"
// components/something-list.scss app-something-list { color: blue; }
BTW i think we have the same exact problem with neil-119 - 3rd party library code and better way to override visual than "super ultra selectors"
@montella1507 I'm not sure that I understand your example. I wouldn't recommend targeting the top level app's selector from a child component. I think that it's best to only style down within your component tree (not up).
Here's a real example (html)(ts) of what I mentioned above.
Tired of seeing stackoverflow answers that say 'don't use ::ng-deep because it's deprecated'. That's just delusional right now. Use it.
::ng-deep is going to hold the web record for long-lived deprecated API.
Tired of seeing stackoverflow answers that say 'don't use ::ng-deep because it's deprecated'. That's just delusional right now. Use it.
I don't see why it should be deprecated in the first place. Right now it's just an angular thing, telling the angular compiler not to scope that style to the component and it's not related to browsers' implementations.
https://update.angular.io says
both are deprecated but using ::ng-deep is preferred until the shadow-piercing descendant combinator is removed from browsers and tools completely.
The deep selector was decided to be remove in in April 2015 by WAWG. ::ng-deep was remove from Chrome starting v63 in Dec 2017.
Surely the Angular team will follow through on that any day now /s
I am also using ::ng-deep in project. It would be nice to have an official answer if its gonna be deprecated from Angular.
@gkaravitis it's already deprecated. It's not clear when it will be removed.
@Splaktar Let's see... As of today 533 repositories use it and 44k code files in github alone. I don't think it can go anywhere anytime soon!
Educating people for the best use cases is more important - eg. when to create a truly global style sheet, or a 'theme' style file (neither of which would need it) and when to actually use it in a component (if absolutely necessary).
@gkaravitis The things are still present the 2 next major versions after they are depreciated. After that, they are candidates for removal. So if something was depreciated in version 7 for example, it's possible to expect that it could be removed in version 10. https://angular.io/guide/releases#deprecation-practices
@mlc-mlapis https://angular.io/guide/deprecations#index has more specific information for planned removal of deprecations.
@mlc-mlapis @Splaktar So after investigate it a bit and with your valuable contibution we decided that its best even on css level to have separation of concerns. We planned to refactor our code and omit the :ng-deep.
@gkaravitis could you share your solutions for your refactor? So far, I've been using global classes on app.component ViewEncapsulation.None but that's just messy. Any component level solution?
@evanjmg thought I'd chime in fwiw. After eading this thread I replaced /deep/
with ::ng-deep
. My belief is that ng-deep is going to exist (within Angular) for quite awhile.
@BenRacicot ::ng-deep is not the recommended solution and it will be removed eventually. This issue is about finding an alternative. I noticed that they say Until then ::ng-deep should be preferred for a broader compatibility with the tools.
- but that's not a solution as it's going to be deprecated nonetheless.
The most practical step right now would be to add a section in the docs explaining each common use case for ::ng-deep
and what are the pros / cons of any alternatives for each.
This issue will be impossible to reconcile if we don't have a list like this. And worse, people get scared by the word 'deprecated' and jump hastily to the first solution they find on StackOverflow.
If everything is outlined in a detailed tutorial it'll help steer people away from the worst footguns - and make it easier to migrate in future to whatever comes next.
eg.
- Never ever ever use
::ng-deep .something
at the top level of your styles. They leak globally, only after you've first viewed that component. This 100% should never be done. You are creating a global style, applied lazily. - Instead use
:host ::ng-deep .mdc-button
so that override is scoped to your component. - To tweak material styles globally => use global stylesheet, or theme mixins
- To tweak material styles on an opt-in basis => use global stylesheet with a custom class applied to the component (eg.
mat-form-field.compact
). This works great if the tweak you made eventually becomes a feature. - To tweak material styles inside a specific component => use
:host ::ng-deep .third-party-component
- To set styles for the first child of a
router-outlet
=>:host ::ng-deep router-outlet
is probably ok. - To set styles for something that was dynamically added and therefore doesn't have ng attributes (eg. a youtube iframe) =>
:host ::ng-deep iframe
is fine - Setting styles for a child component in 100 places over your app => probably bad idea!
- Using
ViewEncapsulation.None
is almost never the right solution to this.
The day ::ng-deep
is deprecated - if anytime soon - is the day I stop updating Angular!
Also I agree with @BenRacicot that it is going to exist for quite some time - years minimum. Angular is mature enough now and way too many projects rely on this (whether good or bad) and have too much momentum to be re-architected overnight.
Let's recap...
- Is there an alternative shadow piercing descendant selector (SPS)? Doesn't seem to be. So as described, we're stuck with what remains (ng-deep).
- We could use globally scoped styles (from a global stylesheet)
- We can set a component's style encapsulation to allow its styles to affect children
- CSS Custom Properties can "cross" shadow boundaries but by no means replace SPSs
Are there other options I'm missing?
ViewEncapsulation.Native and Shadow Dom is the only correct solution but it lacks pollyfills at the moment correct? https://github.com/angular/angular/issues/23636
@evanjmg Given that these 'deep' things get deprecated in the first place because they break encapsulation - it very much concerns me that if everything was suddenly Native
it just wouldn't work for the way people currently use third party components.
The Angular material team is quite stubborn (not trying to offend) with their adherence to the spec that they release components that just don't work for certain designs (try positioning half a dozen text fields vertically with their default spacing) - and therefore necessitate 'tweaks' usually with ::ng-deep
. The alternative is for component makers to add dozens more configuration properties which doesn't seem their style at all.
If material suddenly started using Native
with a polyfill then wouldn't that mean ::ng-deep
or even global stylesheet would be impossible (I'm not sure but isn't that the whole point of shadow dom?).
The first stackoverflow question I referenced above doesn't even mention angular - so I'm just realizing now there's a whole parallel conversation going on in the 'web-component' world to seek inspiration from :-)
@simeyla we see eye to eye on this directly. I pulled material from my project after a ~month.
There does seem to be several tricks as alternatives to piercing selectors but some make me cringe a little:
- Apparently many styles are still inheritable inside a shadow DOM element.
- :host-context(.darktheme) is powerful but I believe it has unusable support
- Leveraging CSS vars as "style hooks"
I'm still going to keep using ::ng-deep until it's unusable.
There is also the ::slotted pseudo-element, which was implemented for ViewEncapsulation.Native (#11595). For ViewEncapsulation.Emulated the pull request (#31547) is currently waiting for review.
Re: @simeyla
eg.
- To tweak material styles globally => use global stylesheet, or theme mixins
For many styles this can be accomplished using the built in theming or typography features of Angular Material. For those where this doesn't apply, my suggestion here is recommended.
- To tweak material styles one time for a very specific component =>
::ng-deep
would be ok here IMHO- To set style (eg. flex) for the first child of a router-outlet =>
::ng-deep
router-outlet is probably ok.- To set styles for something that was dynamically added and therefore doesn't have ng attributes (eg. youtube iframe) =>
::ng-deep iframe
is probably ok
My suggestion here is recommended over these uses of ::ng-deep
.
- Setting styles for a child component in 100 places over your app => probably bad idea!
If this is what you are facing and these styles are the same in every place, then a global style may be the correct approach. If that isn't viable, then you could wrap the external third party component in a first party component that just applies these styles (via my suggestion here).
The day
::ng-deep
is deprecated - if anytime soon - is the day I stop updating Angular.
As mentioned above, there are clear, functional alternatives to ::ng-deep
. You should not be depending upon it heavily in your apps.
I mean, someone did submit a ::slotted() pull request.. https://github.com/angular/angular/pull/31547
Just needs to be approved by someone that can. :)
Is /deep/
removed in Angular v.9? I can't build old library with this css selector in latest RC.3 or RC.4.
Thanks!
Is
/deep/
removed in Angular v.9? I can't build old library with this css selector in latest RC.3 or RC.4. Thanks!
They were planning to remove it: https://github.com/angular/angular/issues/17867
@kolkov I don't think so. Can you provide more details in a StackOverflow post or somewhere else? I.e. the error message, etc.
I believe the solution here is ::slotted()
for when a component wants to style elements projected into it. And ::part()
for when a component consumer wants to style parts of a component. I'd assume this is what the Angular team would implement as a replacement for ::ng-deep
.
Of course this requires both sides to work together to expose the API, but that is what you'd have to do in any other situation all across software development. If you own the component, implement the API. If you don't, submit a request to the developer or fork the project. This situation is no different. We should maintain that encapsulation.
Like in other languages, there's often ways to hack into it and force it to work (e.g. reflection). In Angular, you can either 1) put your styles globally (for emulated) or 2) run some JavaScript inside the shadow DOM (for native).
@Splaktar not sure your solution is quite good.
Container component usually has not only child third-party component we wish to restyle. So view encapsulation with none
value will disable styles encapsulation for everything else inside component. And if on current page there is something with the same selector - it will break styles.
Furthermore Angular doesn't remove style tag from head for destroyed component. So if you have routing in your app, which I pretty sure most do, on other pages these non-encapsulated styles will crap something in case selector matched, which is likely to happen in quite a big app
@gudzdanil if you turn off view encapsulation, you would also scope your component's styles like this:
my-component {
// all of your styles
}
This works fine for leaf components, but not ones that project content.
I understand the need to deprecate ::ng-deep
, that would be because it leaves the application open for unwanted CSS spread through your whole application (...) however the combination of :host
and ::ng-deep
is perfect, why don't you (angular team) create your own :host-deep
pseudo-class selector?
Most developers complaining here doesn't need the ::ng-deep
feature, they need the :host
::ng-deep
combination, by now i think this is the best answer here on this thread.
so for now, ng-deep
still mark deprecate, what a solution for ng-deep without using encapsulation. I want to custom some angular material component only inside my child component
I understand the need to deprecate
::ng-deep
, that would be because it leaves the application open for unwanted CSS spread through your whole application (...) however the combination of:host
and::ng-deep
is perfect, why don't you (angular team) create your own:host-deep
pseudo-class selector?Most developers complaining here doesn't need the
::ng-deep
feature, they need the:host
::ng-deep
combination, by now i think this is the best answer here on this thread.
Exactly. I don't think I've come across a point where I need to style something outside of the host context. It's always been child components.
I understand the need to deprecate
::ng-deep
, that would be because it leaves the application open for unwanted CSS spread through your whole application (...) however the combination of:host
and::ng-deep
is perfect, why don't you (angular team) create your own:host-deep
pseudo-class selector?Most developers complaining here doesn't need the
::ng-deep
feature, they need the:host
::ng-deep
combination, by now i think this is the best answer here on this thread.
So is there a current replacement for :host ::ng-deep
?
No. A possible solution is #31547 (emulation of ::slotted selector), which is waiting to be merged (or reviewed again).
So is there a current replacement for
:host ::ng-deep
?
Just ignore the deprecated warning
Angular team, please ask the Polymer team how to get this done. Apis for custom CSS properties on a component are a few years overdue here.
Most of the times, using ::ng-deep
without a preceding selector or the :host
pseudo-class is wrong, as most of the times you shouldn't style things from your component which are out of its scope. That's clear.
However, what should you do if you need to style elements which are dynamically added to the page outside of your component? A good example is a tooltip, modal or popover that you're controlling from your component but it is appended by whichever library directly under the body.
You could surely define a global style in the app's css, but what if you prefer using different looking tooltips across components?
We solve this issue by giving the tooltip a custom class with the component's name (most of the libraries like ng-bootstrap etc. offer such option), then in the component's css you can write ::ng-deep .tooltip.someComponent { ... }
. This way there are no conflicts, even if there's multiple components on the page at the same time, they can display tooltips from the same library with different styles.
After the functionality of ::ng-deep
will have been removed from Angular, I have no idea how we could achieve this.
@swirlsky I suppose you need to do positioning in global css for tooltip because it's a common behavior, and in components you need to define a template, use ViewChild to query template and then instantiate tooltip with this template. Then angular compiles template with scoped css attributes and you can define styles as usual without ng-deep
After the functionality of ::ng-deep will have been removed from Angular, I have no idea how we could achieve this.
You would just use a global style that is wrapped by the class name of the tooltip type that you are styling. I.e.
.mat-tooltip.someComponent { ... }
Is your concern that you want to have hundreds or thousands of different components that have differently styled tooltips? And they would all be completely custom, so that the below doesn't apply?
what if you prefer using different looking tooltips across components?
Then you would use a class name (which I would always recommend) instead of the component name (not recommended). I.e.
.mat-tooltip.info { ... }
.mat-tooltip.alert { ... }
As you noted, with Material, this would be set via matTooltipClass="info"
and Bootstrap and other libraries have a similar feature.
You shouldn't feel like global styles are banned or not to be used. Of course, you should avoid over using them and make smart decisions about how you name and organize them. For large teams, there should be clearly documented guidelines for global styles.
@swirlsky I wrote about the proper CSS strategy to avoid this issue and many others. Angular CSS Strategies (with Stackblitz examples). It's very simple, style general styles within a global stylesheet. Then resetting styles inside a component is not an issue at all.
Note: This is also how CSS should be architected in general. If your team is relying on component scoped styles FIRST then your problems have only just begun anyways.
@neil-119 you are missing the best option...
Rather than using global styles (and having a huge
src/app/styles.scss
file), given you want to use such a library component in a component:
- define a class on the component's container element
- set the component to
encapsulation: ViewEncapsulation.None
- wrap all styles in the component (SCSS) with the class of the component's container element
- target any selectors that you want in the library component
This allows you to style that library component differently in different parts of your app rather than having a single global style.
Note that in some, concise, isolated cases, the single global style is the best approach.
This can be used to eliminate your dependence upon
::ng-deep
.
@Splaktar nice!
I made alternatives to step2.
constructor(el: ElementRef) {
const { nativeElement } = el;
nativeElement.className = nativeElement.tagName.toLowerCase();
}
OR
@HostBinding('class.app-header') addHostClass = true;
This way I don't have to add an extra container and could still access it as "host/selector" in the scss definition.
The hostbinding approach is a little hardcoded. The constructor approach is more dynamic and extensible -- you will always get your component selector as class name.
Made myself a gist, for future reference.
Why then just dont use komponent selector as CSS selector then? app-comp1 {}
I think if we can implement ::part
(https://github.com/angular/angular/issues/22515) then we could document that as an alternative to ng-deep
and that might also give us the confidence to finally remove the ng-deep
and associated features.
@BenRacicot - I like your strategy, although it only works for "emulated" component encapsulation. If you used ShadowDOM encapsulation, then these global styles do not reach into the template. That being said, neither do the ::ng-deep
styles so there is no loss there.
Hey @petebacondarwin! Yes all true, you've inspired me to update the article and create a Stackblitz to prove out the solution to ShadowDOM encapsulation.
Maybe you've had a chance to see my article and the other Stackblitz templates? (any input would be great!) Within global styles there should also be a CSS custom properties strategy that can be used within the shadow.
Updated the Angular CSS article.
Interesting idea @BenRacicot to use CSS custom properties. I had not even heard of those before.
The [New] State of CSS in Angular has a section about Future of overriding styles:
CSS Variables open the door to well-supported APIs for component customization, allowing developers to shift away from CSS overrides and
::ng-deep
.We recommend introducing custom variables in your libraries and dependencies in order to create an API surface for customizing libraries without the need for
::ng-deep
. Implementation of custom variables allows developers to have more control over their styling and provide a path away from CSS overrides and::ng-deep
.
::part
looks like a good native solution but I wonder what about material or other 3 rd party components where current projects use :ng-deep
. The only problem imo is auto migration of :ng-deep
to :part
.
End up in this thread after looking up this issue and ::part is certainly not an option because this requires templates to have matching attributes. Cannot expect all third party libraries and content to add a custom part attribute to every single element because you would not know what someone may want to style there.
Angular could automatically wrap component styles with the component selector and move to global scope. So any inline or styleUrls styles are always wrapped by the compiler. This way nothing leaks, no globally written styles or dom piercing hacks are required.
@neil-119 you are missing the best option...
Rather than using global styles (and having a huge
src/app/styles.scss
file), given you want to use such a library component in a component:
- define a class on the component's container element
- set the component to
encapsulation: ViewEncapsulation.None
- wrap all styles in the component (SCSS) with the class of the component's container element
- target any selectors that you want in the library component
This allows you to style that library component differently in different parts of your app rather than having a single global style.
Note that in some, concise, isolated cases, the single global style is the best approach.
This can be used to eliminate your dependence upon
::ng-deep
.
I am sorry but why is this or any other of the alternative solutions not mentioned in the angular docs, right where ng-deep is? Am I missing something? Usually when something gets deprecated, the documentation gives you the alternative 😅
I've been working for the past 8 months for a project that required Angular Material as its main components library. The problem is, at least 30% of the components need a certain level of customization to match de design, thus I need a way to override the nested styles of each one of these components. Of course the first thing that came up to my mind was using ::ng-deep, easy, quick and does the job. Now that ng-deep is deprecated, not supported in any way, browsers break the styles when you use them, and there is no information about clean alternatives in the Docs AFAIK, has anyone gone through something similar? any alternatives you could find? Everything helps.
I'm finding that the most common workaround to this is to somehow use ViewEncapsulation.None
in a wrapper component and put all the ::ng-deep
styles in there. We could not make a bit easier by doing something like this.
@Component({
selector: 'app-some',
templateUrl: './some.component.html',
styleUrls: [
// The suggestion
{ path: './some.component.deep.css', encapsulation: ViewEncapsulation.None },
'some.component.css'
]
})
export class SomeComponent implements OnInit {
// ...
}