- 4 minutes read

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:

constructor(private renderer: Renderer2, @Inject(DOCUMENT) private document: any) {} ngOnInit() { const styles = this.document.createElement('STYLE') as HTMLStyleElement; styles.id = 'dynamic-theme-css'; styles.innerHTML = this.style; this.renderer.appendChild(this.document.head, styles); }

Reacting to changes

Now it's time to implement the ngChanges event listener:

ngOnChanges() { const styles = this.document.getElementById('dynamic-theme-css'); if (styles) { styles.innerHTML = this.style; } }

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.


Comments