Creating DRY Style Sheets in Angular Elements

Travis Jones
4 min readFeb 21, 2021

--

Introduction

I’ve recently started looking hard at the use of Web Components and how to make them performant. In my case, the preferred method for building them is via Angular Elements. Initially, I began trying to simply convert a component from a regular Angular SPA (Single-Page Application) into a Web Component. This exposed some major problems, mostly amounting to the component not being architected to be a Web Component. This brings me to where I am now: exploring different architecture/strategies to find the best fit. For this article in particular, my focus is on making sure that style sheets remain DRY (Don’t Repeat Yourself).

The Problem

If you’ve worked in Angular, odds are you’ve worked with an SPA. In this setting, you’ve likely used global styles as a set of common rules that penetrate to all of your components, so that they aren’t repeated for every component. Then you have your specific component styles. Makes sense.

Now we decide that we want to make use of the trendy, cool, and very useful Web Components. But we want to continue with the familiar Angular, so we leverage Angular Elements to build our Web Components. That’s cool, but suddenly the game has changed.

What’s changed? Well, at least in regards to styles, you’re using the Shadow DOM for CSS encapsulation. This means there’s no (or very limited) access to global styles. If you try to use them you’ll…

  • …bleed styles onto a page where they don’t belong.
  • …not actually get your web component(s) styled.

So how do we handle this? Well, first, there are some things we want to avoid:

  1. …bloating the Web Component’s size
  2. …duplicating styles in our style sheets at coding time
  3. …duplicating our styles at compile time
  4. …duplicating our styles in memory at runtime

Points 1 & 2 are both goners if we start writing or importing common styles directly into each component’s style sheet. So, let’s not do that. Instead, what you can do is make what I call “semi-global styles.” These mimic global styles in that they penetrate and style sub-components, avoiding the need to repeatedly import basic styles per sub-component, but will also be scoped to the Web Component, so that they don’t bleed out onto the page.

What are some ways we can do this? Well, I’ve explored this a bit here, if you want to give it a try. If not, that’s fine. I’ll walk through the basics of it.

Obvious (not so good) Approach: Bundling Styles

The first method, is to import the styles into your Angular component’s styles, like you’re taught in the documentation. (Note: there’s little resulting difference in this approach between listing the style sheets in the styleUrls array and importing the various style sheets into a single style sheet.)

This is easy and makes sense. But there are some issues. When you compile an Angular component into a Web Components, a new composite style sheet (or string) is generated from all those listed in the array. So, say we’ve got 2 distinct Web Components that we’re throwing on the same page that share some common styles. When they’re compiled, each one will contain duplicates of the common styles. This hits on point 3, “avoid duplicating our styles at compile time.” And, of course, this impacts your bundle size (point 1).

Now, let’s make things worse… If you load multiple instances of these same Web Components onto your page and look at their Shadow Roots in your web tools, you’ll see style tags containing all your semi-global CSS repeated for each instance, hitting on point 4 (duplication in memory).

If you’re working with a Web Component that has simple styles, the amount of bloat may be acceptable. However, if you need a more long-term and sustainable approach, we need to try something else.

Better Approach: Lazy-Loaded Styles

If you’ve been working in Angular for a long time, it may be easy to overlook a simple mechanism we could use to avoid duplication:

<link href=”…” ref=”stylesheet” type=”text/css” />

Regardless of how many times a same style sheet is imported using a link tag, it’s only going to downloaded once. Additionally, a link tag can be dropped inside a Shadow Root and keep the styles scoped to it! This seems much more manageable, if not particularly Angular-y.

Let’s start by registering some lazy style sheets with in our angular.json, so that they can still leverage style preprocessing.

By adding the above entries into the styles array, and setting inject: false, we’re ensuring that the style sheets are not bundled together with the JavaScript. They are their own files, that can be linked to as /stylesheet1.css and /stylesheet2.css. So, let’s go ahead and do that.

That’s the gist of it. By doing this, we’ve got reusable style sheets that won’t get duplicated in code, in the bundle, or in memory.

Important Note: It’s worth mentioning that this approach does have a drawback, if you’re not prepared for it: the deploy-URL. When leveraging the deploy-URL option when compiling with the Angular CLI, it will search for any instances of url(...) within your CSS, and automatically prepends the deploy-URL to the appropriate instances. This is NOT the case with HTML. If you’re using this approach, you may need to account for this. Two approaches that could work well are a post-processing script or concatenating the value of your deploy-URL from your config.

--

--

Travis Jones
Travis Jones

Written by Travis Jones

Front-end Software Architect

No responses yet