Harness the power of Web Components with Scala and Laminar

Antoine Doeraene
5 min readNov 1, 2023

Many applications today are built with Web technologies. The reason is simple: everyone has a browser installed on their machine. Moreover, even though there are different browsers, they all follow common specifications. Everything is based on the triplet HTML/CSS/JavaScript. Therefore, there is often very little work needed to adapt to a huge audience.

Photo de Pankaj Patel sur Unsplash

In particular, browsers offer a large panel of special HTML tags that they understand. For example, a buttonis indeed always displayed as an actionable piece of UI, responding to mouse clicks as you might expect. Sometimes it goes further. Many people learn (the hard way) that forms have a very specific behaviour when we “submit” them.

However, given the increasing complexity of web applications that we are seeing today, these built-in “components” are not enough. Frameworks such as Angular, React or Vue understood that a long time ago. Hence, they give developers the option to build their own components. But these components are bound to their framework. For example, you can’t easily user a React library of components from Angular (the other way is even worse). Luckily, though, browsers offer a tool to circumvent this problem: web components.

We are not going to talk about building these components. Rather, we are going to see how one can leverage web components in Scala, with the Laminar library.

Web Components

Web Components are a technology enabling one to extend the native “components” (buttons, forms…) by creating new ones, as powerful as one can hope for. The fact that they are a web standard is also important for the future-proof guarantee it brings to the table.

The way web components work in detail is not relevant for us here. However, the way you use them is. Web component creators can define a number of attributes that control their behaviour, exactly the same way as you can set the disabled attribute on a button to stop it from interacting. Web components can also define custom JavaScript events that they will emit, similar to the input or mouse-enter events.

For example, imagine that you define a web component whose tag is <stars-rating> with two attributes, max-stars and value. That way, you could insert in your HTML code (or manipulate the DOM to have it) the following: <stars-rating max-stars=5 value=5></stars-rating>. And it would be rendered as a rating indicator, of 2 stars out of 5. This example is inspired by the Rating Indicator of the SAP UI5 library (more on that later).

With this example, you can see why any framework/library that is able to maniplulate the attributes of elements in the DOM, will be very effective at integrating web components. Even server-side rendering or jQuery can do that! And, in the case we are interested in, the Scala library Laminar can do it.

Laminar

Laminar is a Scala.js library designed to manipulate the DOM from the comfort of your Scala code. The core design of Laminar is precisely to build a powerful DSL enabling you to manipulate what tags (elements) you insert into the DOM, and what attributes you give them.

Let’s come back to our stars-rating example from earlier. In Laminar, you could imagine the following piece of code.

val ratingVar = Var(0)
StarsRating(
_.value <-- ratingVar.signal.distinct,
_.maxStars := 5,
_.events.onChange.map(_.target.value) --> ratingVar.writer
)

This will keep in sync the number of stars selected by the user with the content of the variable ratingVar. This variable can then be used as part of the submission for a form, for example.

If you know Laminar, you can notice that this syntax feels very familiar. And for a good reason. From the Laminar point of view, there is nothing that distinguishes between a native html tag and a web component.

You might still wonder where the StarsRating function, or the _.maxStars may be coming from. We are going to elaborate on that in the next section.

Declaring Web Components for Laminar

If you ever had the chance to take a look at Laminar’s code base, you probably saw that there are essentially two parts. The first part (and probably the most important) is the machinery. It defines all the ways end users can control the DOM, the tags, the attributes, the events… The other one is barely (so to speak) the declaration of all the built-in things that exist in the browser. That is, all the tags, all the events, all the attributes, all the style attributes, and so on and so forth. The main point is that the machinery is completely decoupled from the actual things that exist. We can easily add the definitions for any web components that we want to use.

How does it look like, concretely? Consider the following declaration for our stars-rating component:

Let’s break this down step by step:

  1. We first define what type of underlying JavaScript element the web component will have. In this case, it’s an html element (like all web component) but in addition, it has a `value` member (of type Int).
  2. We define that the html tag of the component is stars-rating , so that Laminar knows that it has to insert a <stars-rating> tag when we use it
  3. We tell Laminar all the special attributes that this component can have (in addition to all the usual attributes)
  4. We say that this web component has an on-change event that can be triggered
  5. And finally, we define a function that we can call to actually create such web components

And that’s it! With these few lines of code, we can use the stars-rating component within our Laminar code. Finally, let’s have a look at a concrete case study, with the SAP UI5 library.

A case study: the SAP UI5 library

As mentioned in the introduction, web components are very convenient, both for library maintainers and users. Because they don’t need to “agree” on a framework to use, being a web standard.

In particular, a Laminar user can choose any web component library they like, write these definitions on top, and they will be good to go. A library that I personnally enjoy a lot is the UI5 library from SAP. They have a high number of premium quality components, with outstanding documentation to back it up.

In fact, I like it so much that I wrote a library for Laminar users containing the definitions for all the components and features of this library.

If you are interested, you can take a look at these definitions by yourself. You will notice that writing those is straightforward. If you are curious, you can also take a look at the live demo, implemented with Laminar and the library.

Closing words

The key take away of this blog post is that, if you are a Laminar user, and you are looking for ready to go components to use and fasten your development, you can search for any web component library out there. Besides SAP’s UI5, two other examples are Google’s Material UI and Shoelace.

If you are not a Laminar user but rather, let’s say, a Tyrian user (or any other Scala-js UI library), you might wonder whether the same kind of bindings can be done. The answer is definitevely yes!

--

--