CSS variables in Angular: Part 2. ng-deep is no longer needed
A powerful alternative for replacing ng-deep
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;
}
}
Currently, when we change the theme, the component does not pick up our styles and remains unchanged
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
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
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
- CSS variables in Angular: Part 1. Forget about SCSS variables
- CSS variables in Angular: Part 3. Charts