head image CSS variables in Angular. ng-deep is no longer needed: Part 2

CSS variables in Angular: Part 2. ng-deep is no longer needed

A powerful alternative for replacing ng-deep

Maksim Dolgikh
4 min readDec 25, 2023

--

Previous part

External library component

Let’s imagine that we are developing an Angular library of UI components for the rest of the teams.

We have a simple component that displays a header and a description

@Component({
selector: 'lib-external-component',
standalone: true,
imports: [],
template: `
<h2 class="header">{{header}}</h2>
<span class="description">{{description}}</span>
`,
styleUrl: './external-component.component.scss'
})
export class ExternalComponentComponent {
@Input()
public header = 'External component header'


@Input()
public description = 'External component description'
}
:host {
display: block;
padding: 8px 12px;
outline: 1px solid #b7b7b7;


.header {
font-size: 18px;
font-weight: bold;
color: red;
}


.description {
font-size: 14px;
color: blue;
}
}
Сomponent visualization
Сomponent visualization

Currently, when we change the theme, the component does not pick up our styles and remains unchanged

Switching topics, has no effect on the component
Switching themes does not affect the component

How can we style .header and .description?

There are three options:

  • Customize globally via a generic styles.scss file
  • Customize the style of the component in the parent component style with ViewEncapsulation.None
  • Customize the style of the component in the parent component by wrapping that in ::ng-deep

The first two options may be used depending on the situation and if they do not break the overall styling logic.

But ng-deep is a deprecated selector, and developers continue to use it as a “magic pill” for accessing styles of a child component.

Overriding styles in a parent component with theme support may look like this

@each $theme-key in map.keys(variables.$themes) {
$theme: map.get(variables.$themes, $theme-key);


:host-context([data-theme="#{$theme-key}"]){
::ng-deep {
app-external-component {
.header {
color: map.get($theme, "primary");
}


.description {
color: map.get($theme, "secondary");
}
}
}
}
}

Let’s check the support of our themes for the external component

Switching themes affect the component
Switching themes affects the component via ng-deep

We got the result we wanted but created a lot of complex code to style the component, which inherited all the styling issues for generic styling

Is it possible to simplify all this and even get rid of using ::ng-deep?

Сross-cutting styling of the component

I suggest replacing the old implementation of styles for the external component with a new one that will use CSS variables instead of fixed values

:host {
display: block;
padding: 8px 12px;
outline: 1px solid #b7b7b7;


.header {
font-size: 18px;
font-weight: bold;
- color: red;
+ color: var(--external-lib-primary, red);
}


.description {
font-size: 14px;
- color: blue;
+ color: var(--external-lib-secondary, blue);
}
}

To make the component work reliably, I passed a fallback value tovar() if CSS variables are not declared higher up the DOM element tree. Thus, our component will work even without CSS variables

Let’s also change the parent component styles that style our external component.

Instead of using the SCSS variables from the old implementation, I will already use the global CSS variables of our application.

- @each $theme-key in map.keys(variables.$themes) {
- $theme: map.get(variables.$themes, $theme-key);


- :host-context([data-theme="#{$theme-key}"]){
- ::ng-deep {
- app-external-component {
- .header {
- color: map.get($theme, "primary");
- }
-
-
- .description {
- color: map.get($theme, "secondary");
- }
- }
- }
- }
-}

+ --external-lib-primary: var(--primary);
+ --external-lib-secondary: var(--secondary);

Result

Switching themes affects the component via css-variables
Switching themes affects the component via CSS variables

Just two CSS variables, and the closed component is already styled to our needs 🔥.

Conclusion

Using this simple example, I showed that for the convenience of third-party Angular developers, it is enough to specify CSS variables use of actual values, which our component will target.

The more the better. This rule fully works for the rest of the UI library components and provides an open-closed principle.

This approach will avoid in some cases:

  • unnecessary generation of styles
  • providing API styling via mixins
  • use of ng-deep

Third-party developers have the right to decide when to declare CSS variables for customizing components:

  • One-time globally, for consistency
  • Local declarations in the parent component, for specific styling

The only caveat is that you need to be careful when creating css variables, because you never know in what context your component will be created, and styling conflicts can occur. Try to create names for css variables that begin with your scope

Resources

Other parts

Links

--

--