Playing around with webcomponents
Posted | Reading time: 5 minutes and 4 seconds.
As a regular reader of my blog, you might be aware that some time ago I went down the rabbit hole to bring pictures on a raspberry pi. In the course of this little exercise, I accidentally created my own little dashboard application to show pictures from my nextcloud server, and extended it over time to display the current time, remind me to bring out the trash and to be more flexible on the images. Eventually, it became too complex to have it in one file, so I had to come up with a strategy to maintain this project for longer. Maybe I’ll talk about this in a future post.
At work, quite a few people have started working on web components. Web components are a collection of tools that, if used together, act as a framework which allows you to create reusable HTML elements that you can use in your application without breaking it. So let’s put that to the test.
A digital clock
One of the things I display is a digital clock. Displaying the digits of the clock should be in such a matter that the characters don’t jump if the 1 is slimmer than the 0 in the font.
To avoid this, each number is placed inside an element that has the same width. The original, naive approach was the following: remove all elements in the container and add each digit as an element. It worked, but performance wise this solution is not ideal - even though usually only one digit is changing, all elements have to be recreated.
Making sure that only the digits are updated that have changed is much more difficult. Keeping that in sync with the HTML dom even more so. This is where web component may be able to help us.
web components
As pointed out, web components come with a set of tools. We are planning to use 3 of them: templates, shadow dom and custom elements. Templates will help us define the basic HTML, shadow dom to avoid leaking CSS definitions into other parts, and custom elements to have the single, updateable elements in place for efficient rendering.
creating the custom element
A custom element is, as the name implies, an HTML element that can be created for custom use cases. Looking at our scenario, two components come to mind. One that holds the digit, and only updates if it changes, and one for the composition of all the digits, and to manage the time.
Let’s start with the control for the digits.
To create a custom component, first, a class has to be created that inherits from the HTML element.
Our element has one attribute digit
, which we can also access as property accessor. The first draft looks like this:
class ClockCharacter extends HTMLElement {
constructor() {
super();
connectedCallback() {
this._showDigit();
_showDigit() {
this.innerText = this.getAttribute("digit");
}
Now the custom element has to be registered via window.customElements.define("digital-clock-character", ClockCharacter);
in the browser, and then we can use it
To decide if we should change the displayed value we can use a life cycle method called attributeChangedCallback(attrName, oldVal, newVal)
, which receives the name of the changed attribute, the old value and the new. Based on the result we can change the rendered value.
the main difference between React and custom elements is the fact that in react properties/attributes are read-only for the component itself, whereas for the custom element the attributes can be set. Outside of the methods that receive the request to change them this should probably be avoided, as attributes of HTML elements do not tend to change from the inside. As an example, you probably would not expect that a div that you are using is magically changing their class name. Similarly, nobody will expect this from your element.
Our component looks like this now:
To fulfil the requirements of the non-jumping characters we will add styles as part of a template, and to make sure those styles will not be overwritten nor influence the other elements on the page, we will put them into a shadow dom. But first thing first.
Currently, the custom element is pretty flat. Creating the HTML and style elements in javascript requires quite a lot of writing and wiring, so having a template with all the elements can make our life easier and keep the code clean of presentational logic.
The template is created via the HTML template
element. As we have the span now, we can create a second element, and see how the numbers no longer jump.
When we apply that to the dashboard and look at the console, we can see that the updates are much less
By now I have replaced all the elements by custom elements. The gallery, the clock and the reminder are independently in their shadow dom on the page.
Using this “in real projects”
Getting started with web components is easy, but even in such a small project, some issues popped up. For instance, when I first moved the elements into the shadow dom, their styles would be “all weird”. The reason was, that the normalize.css
stylesheet was no longer applied. This might as well become an issue for things like style libraries. A simple example of a company header component shows the issue. While the font is globally specified, it will not be applied to the header:
One solution might be to always add a link to the global stylesheet in the header, as the stylesheet would be cached the hit wouldn’t be that bad. It does mean that other custom-elements cannot be easily styled if they don’t expose a way to inject styles.
The other thing that worries me is how easy it is for inexperienced developers to create a that might be unexpected, like changing the attribute value from inside the custom component. An example would be:
As this can be done, it will be done, as it provides an easy way to communicate to the outside world. Using the more controlled way of dispatching custom events, just like this:
But this is much less straight forward and for beginners, not the first thing to think about.
It is to hope though that these issues will be less visible when using web components from frameworks that have solved those issues, and in that case using them will add allow the web to become much more modular and improve the ability to share components.
On top of that I have created the library pure-lit that provides a more functional wrapper around this, and has one-way bindings similar to react. Feel free to take a look!