Angular 10 - Avoid using ::ng-deep (ngdeep)
John Peters
Posted on August 3, 2020
Problem
Attempts to get CSS specificity within the Angular View Component's CSS sheet often fail. Our Style markup just can't go deep enough to find the elements we want. We know we're doing it right because our Javascript based QuerySelector works, but trying the same selection in CSS of the component just fails!
Environment
We used SCSS for the core, but all Views were using CSS.
Background
First, what is deep? I found issues when attempting to override stylings mostly for Material Components but, have had my own (parent) components present challenges when reused elsewhere. Let's just call "Deep" any styling not directly related to the current component.
CSS Query Selectors within a View's component style, are being ignored by Angular anytime we attempt to change a "deep" style. With Angular, it's simply wrong to assume we can affect "deep" styles within any given component.
Solution
If we want addressability to any style in the project, we simply move our markup to the root level SCSS style sheet to accomplish it.
Perhaps it works so well because it bypasses Angular's View Encapsulation rules.
Just don't use NG-Deep; it kind-of sort-of works but all the red flags are out on it and forget going too deep. Just use root level specific SCSS selectors as shown here!
ng-select {
padding: 0.25rem 0 0.25rem 0.25rem;
border-style: none;
border-bottom: 1px solid $Color-BlueGreen;
font-family: $Font-Alternate;
width: 100%;
.ng-dropdown-panel {
background-color: red;
.ng-option:hover {
background-color: yellow;
width: 100%;
}
.ng-option {
background-color: white;
padding-top: 0.3em;
padding-left: 0.3em;
cursor: pointer;
}
}
.ng-select-container {
.ng-value-container {
.ng-input {
input {
// Works every time!
width: 100%;
// Five Levels Deep
}
}
}
}
}
Here's another example of avoiding :ng-deep! This was in the core.scss stylesheet. It worked first time!
app-parent {
app-child-grid {
app-child-edit.cdk-drag {
form {
div {
// 6 levels deep from the app-parent
ng-select {
width: 101%;
}
}
}
.className {
app-custom-control {
// Still had to override this one
justify-content: end !important;
margin-right: 2em;
.buttons {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(3em, 1fr));
min-width: 6em;
margin-bottom: 0.5em;
button {
max-width: 5em;
}
div[name="faIconSave"] {
justify-self: end;
margin-right: 0.7em;
}
}
}
}
}
}
}
The demo above shows 6 levels of depth that altered the style on the first attempt! 10 minutes to perfection and addressability vs. days to try to kind-of sort-of get NG-Deep to work.
*How We Figured This Out! *
Angular's API states that the ng-deep psuedo-class is deprecated.
Furthermore; it states that ng-deep
completely disables view-encapsulation for that rule.
If we use it without the :host pseudo-class, it will make the style-rule global, not a good thing.
There's something odd about Angular view encapsulation which gets style specificity wrong. How do we know? If we write a Typescript QuerySelectorAll we can pull any ID or Class on the page regardless of depth.
But if we use a CSS selector in the component's StyleSheet, looking for the same ID... Angular doesn't find it when the depth is deep. This, to me is a design flaw.
This forces us to write Typescript QuerySelectors for our component's ele.NativeElement to narrow the search; but we don't really want to do that. We prefer all styling in the StyeSheet of the component.
Old Solution Was
If we ignore the ::ng-deep deprecation warning for now, (after all, it's still working in Angular 10); we come up with specific rules following this format.
// Note we don't need the ID
// We just go for the className
// This still allows for cascading
:host::ng-deep.className{
width:5em;
}
This code functions the same as using a query selector, removing the old class name and adding in the new class name:
let element =
ele
.nativeElement
.querySelector('.className')
ele.class.remove('oldClassName');
ele.class.add('newClassName');
We can spend a ton of time trying to write more specific CSS selectors (no guarantee that Angular's View Encapsultion) will find them, or we just use this pattern...
:host::ng-deep.className
:host::ng-deep.#IDName
Best Option
We found the best option is to use Less or Sass to build very specific style rules, this works better than ng-deep!
JWP 2020
Posted on August 3, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 20, 2024
November 15, 2024