Wouldn't it be nice to use TypeScript variables in CSS files, the same way you can do it in the HTML templates? For some reason, Angular doesn't allow interpolation in CSS, but it's easy to implement this feature yourself.
Approaches that don't work
Just for the record, let's quickly examine two approaches that don't work. As tempting as it is, you can't use Typescript variables in CSS:
@Component({ selector: 'app-themed-page', templateUrl: './themed-page.component.html', styles:
div {
background-color: ${theme.color} /* doesn't interpolate the TS variable */
}
h1, h2, p {
color: {{theme.color}} /* doesn't interpolate the TS variable, either */
}
})
export class ThemedPageComponent implements OnInit { }
Neither of these approaches compiles.
There's an obvious alternative. Just put the <styles>
into the HTML templates. Too bad Angular strips every <styles>
from the HTML template.
The do-it-yourself solution
Like so often, a Stackoverflow article has the answer. You can achieve the goal with a little DOM manipulation.
First, let's define a component. It's an unusual component: both the CSS class and the HTML template are empty. So I guess a directive does the trick just as well. I chose a component because I needed to pass several parameters. In our theming example, the idea is to use the component like so:
If the foreground
and the background
aren't constants, but TypeScript variables, the component should update the CSS code.
To achieve the interpolation bit, we put the CSS code in a method and add the two input variables:
@Component({ selector: 'pdf-dynamic-css', templateUrl: './dynamic-css.component.html', styleUrls: ['./dynamic-css.component.css'] }) export class DynamicCssComponent implements OnInit, OnChanges { @Input() public foreground = "black"; @Input() public background = "white"; public get style(): string { return
div {
background-color: ${theme.color} /* doesn't interpolate the TS variable */
}
h1, h2, p {
color: {{theme.color}} /* doesn't interpolate the TS variable, either */
}
;
}
That's a cumbersome way to write the CSS code, but that's the price we have to pay. Either there are autocompletion and syntax highlighting, or there's variable interpolation.
Note that we can't simply put the CSS code in a variable. It has to be a method. Otherwise, we can't react to changes of the @Input()
variables.
Putting the CSS code into the header manually
Next, we put this CSS code into a new STYLE
attribute in the header of the page:
Reacting to changes
Now it's time to implement the ngChanges
event listener:
Removing the CSS when the page is left
In most cases, it's also a good idea to remove the CSS code when it's no longer needed:
ngOnDestroy() { const styles = this.document.getElementById('dynamic-theme-css'); if (styles) { this.document.removeElement(styles); } }Wrapping it up
With a little effort, you can implement dynamic CSS yourself. It's not that much different from what Angular does. Under the hood, the Angular engine injects CSS and HTML code using JavaScript all the time.
In most cases, the performance penalty should be negligible. I've used this approach to react to window resize events. Resizing calls these events pretty frequently. Even so, performance doesn't seem to much of a problem. I'm using this approach to implement dynamic media queries in my PDF viewer. You can see the real-world example on my PDF viewer showcase. You can also see the source code of my real-world example of dynamic CSS on GitHub.