Angular 10 - Avoid using ::ng-deep (ngdeep)
Tags: angular, typescript
Published: 2020-08-03 15:17:15

**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!

```css
 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!

```css
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] (https://angular.io/guide/component-styles#deprecated-deep--and-ng-deep).

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.

```css
// 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:

```typescript
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...

```css
: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