DEV Community

John Peters
John Peters

Posted on • Updated on

Angular 10 - Avoid using ::ng-deep (ngdeep)

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
          }
        }
      }
    }
  }
Enter fullscreen mode Exit fullscreen mode

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;
            }
          }
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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');
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Top comments (4)

 
jwp profile image
John Peters • Edited

Okay just figured out another clue to this puzzle, in the data below this is how the browser style page show the order. Material Components do late CSS binding somehow, it makes things hard to fix because those styles have highest specificity.

.mat-button[disabled], .mat-icon-button[disabled], .mat-stroked-button[disabled], .mat-flat-button[disabled] {
    cursor: default;
}

<style>
.mat-icon-button {
    padding: 0;
    min-width: 0;
    width: 40px;
    height: 40px;
    flex-shrink: 0;
    line-height: 40px;
    border-radius: 50%;
}
<style>
.mat-button, .mat-icon-button, .mat-stroked-button, .mat-flat-button {
    box-sizing: border-box;
    position: relative;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    cursor: pointer;
    outline: none;
    border: none;
    -webkit-tap-highlight-color: transparent;
    display: inline-block;
    white-space: nowrap;
    text-decoration: none;
    vertical-align: baseline;
    text-align: center;
    margin: 0;
    min-width: 64px;
    line-height: 36px;
    padding: 0 16px;
    border-radius: 4px;
    overflow: visible;
}

// This was ::ng-deep but only !important made it work

.mat-icon-button {
    width: 1em !important;
    height: 1em !important;
}

They say don't use important but I know of no other way to get my style rules to the top other than setting them in javascript at the element layer which is at the top of rule list. Everything else was intrinsic to the material controls.

Collapse
 
jwp profile image
John Peters • Edited

Sebastion; I tried :host-context yesterday, it worked a bit deeper but not all the way into a 4 or 5 level deep component. I may be doing something incorrect in my css selectors. Just not sure yet.

Collapse
 
annadore profile image
AnnaDore

In angular material 15 unfortunately these cases are not working. If anybody know how to remove ::ng-deep there - please, leave the comment

Collapse
 
mrezatabaa profile image
Reza Tabaa

How may I view the HTML file?