Hands-on Laminar

Rafał Zelek
SoftwareMill Tech Blog
10 min readApr 2, 2021

--

Laminar is a Scala library developed by Nikita Gazarov that mixes reactive programming and declarative views. A thing worth mentioning is that at the heart of Laminar lives the streaming library called Airstream developed by the same author.

In this post, we will briefly go through the most common cases that every frontend developer needs to do daily. That is styling and forms with error handling. Let’s see how hard or easy that might be with the laminar library.

For the sake of simplicity, I’ll skip how to set up a project with laminar, see https://laminar.dev/documentation#dependencies to set it up.

Let's start with a simple cheat sheet that explains the library API and will serve us as a reference for the next steps.

Getting started

Once you imported laminar with:

HTML tags and CSS properties are within the scope.
To render something, just call arender method with the pointed container and a root node you want to show.

Cheat sheet

import com.raquo.laminar.api.L._ Imports laminar into the scope.
<-- Can be used to bind an HTML attribute with some reactive value.
:= Applies a regular value into an HTML attribute.
--> Allows for an event consumption, can be used to bind an event into a sink.
inContext A function that gives a reference to the current DOM element.
cls Alias for className.

Styling

Basic

Laminar has its own CSS style definitions, so it’s as simple as passing already pre-defined CSS properties as an HTML tag parameter.

Lists

You can also pass the list of the properties and that just works because the HtmlTag accepts the number of parameters def apply(modifiers: Modifier[ReactiveHtmlElement[Ref]]*): ReactiveHtmlElement[Ref] .

Via streams

CSS properties, like any other property, passed into an HTML tag, can also be set on the fly via streams <-- method, which applies the stream into the property.

ScalaCSS — Basic

ScalaCSS is a library that can be found here https://japgolly.github.io/scalacss/book/, it provides type-safe CSS style definitions. Let’s take a look at how it cooperates with laminar.

First of all, define the styles inside an object instance.

To render them on the page, the object needs to be registered on the application startup.

The usage is as simple as passing the class name as a parameter into an HTML tag.

ScalaCSS ⇔ Laminar Integration

The laminar author promises us a simple way to customize and integrate with other libraries and mechanisms via Mod implementation.
It’s really easy to change this long cls := HelloWorldStyles.myStyles.className.value definition into something shorter that looks like a native laminar API.

Define the styles:

Define an implicit method inside a package object to have an implicit conversion from StyleA to Mod.

Now, passing the style object from the ScalaCSS library is as simple as passing it as a parameter into an HTML element. Scala does the rest, as long as the method is inside the scope.

The result

Forms

Inside the laminar documentation (https://laminar.dev/examples/uncontrolled-inputs), there are three examples of how to manage forms.
I want to explore some React-like approach, in which each component is an independent reusable piece of code. On top of that, we will use fancy Scala features to extend our domain language and make the code easier to reason about.

Requirements

The job is to implement a simple registration form:

  • There are 3 mandatory fields: e-mail, password, repeated password.
  • Each field should be validated.
  • An e-mail should contain the @ sign.
  • A password should be nonempty.
  • A repeated password should match the password.
  • The form is validated on leaving a field or on pressing a register button.

Styling

For styling purposes, I’ll use the ScalaCSS library, which was already mentioned above. I want to have some pretty animation to make that form a little bit alive and that’s why the labels will serve us as a custom placeholder that goes to the left top corner when a user starts the interaction with the input field. It’s a similar approach to the one in material design.
The styles are nothing fancy, that’s why I’ll just drop the code as a reference, without digging into too many details.

First of all, let’s define styles for a form, header, and register button.

Now the input fields. I treat them separately because the mechanism of “material-like” labels can be reused across different parts of the system.
To move the label away to the top-left corner, we just add non-empty class to the input field, that’s why we need to use unsafeChild style definitions.

We also need syntactic sugar, which we defined previously because we will use it excessively.

Form skeleton

Let’s create a bare-bones visual implementation that we will improve in the next steps.

Pretty long code for such a simple task, isn’t it?
But anyway, the only moving parts are reactive signals of 3 Variables to add non-empty label into the fields.

This mechanism is pretty generic; write into Var when an event occurs, display class based on Var value:

It’s such a common operation that laminar provides syntactic sugar syntax for it! You can simply write:

In that big chunk of code a few lines above, there is a lot of repetition over here, so let’s break it down into smaller components and share these moving parts.

Input fields extracted

Starting from a top-level perspective, the only things that are different between these input fields are: id, name, type and state Variable.
Having that in mind, we can create a simple API that abstracts away the implementation details we repeated:

Input fields implementation details:

The functionality is the same, but we managed to reduce some duplication.
Notice how Observer is used here to propagate changes from children to the parent.

Observer[A] is a modest wrapper around an onNext: A => Unit callback that represents an external observer

(source: https://github.com/raquo/Airstream#observer)

This is a similar pattern we have in React for smart and dumb components, you can treat that as a simple callback. We want to keep the Input as dumb as possible.

Even though the Inputhas internally state binding between onInput value and cls.toggle , it’s still for purely visual purposes. The only thing that component gives back is a value returned via valueObserver: Observer[String], and it’s his parent job to do something with that state.

Form validation

So far so good, we have something, but it’s still a long road to fulfilling all the requirements.

Validation can be triggered by different actions. It can be done on blur, on form submit, or on change of other input value. That’s why we will use a Signal[A] to reactively update children with validation status.

The idea behind this type of alias definition (type Validator = Signal[Option[String]]) is that the signal generic type Option[String] returns Some("error text") when validation fails or None when it succeeds.

We add validators: Validator* var args to both Email and Password components for nice API. It’s var args because there might be different validations at the same time. E.g. password pattern with each requirement in a different line. Errors component can display more than 1 error for the same reason.
Function Signal.combineSeq, from the code below, simply changes Seq[Signal[A]] into Signal[Seq[A]].

Remember to replace the Email component usage with mocked error:

The styles were already defined for that functionality, so without any further changes, the form should look like this:

Fully functional form validation

We have the visual part done.
The parent (Forms object) needs to know the internal state of the input components to make proper validation on form submit and on blur.

To do that, we will use Observer[A] again.
Not all of them are used in our case, but I’ll show how to implement every input state value.
Let’s define a config class that we’ll pass into the Input component.

Now, the heart of state binding. Laminar gives us the possibility to create a separate Mod instance and just use it as a parameter to an HTML element.
$invalid: Signal[Boolean] is passed as a parameter, because it’s already calculated for the CSS invalid class.

Password and Email just accept the configuration and pass it into the Input component to use it as a parameter to inputStateMod, which again will bind our input state with the parent via InputStateConfig.

Form validation wrap-up

Everything we already did is just dumb visual components composition with callback bindings. Let’s get our hands dirty and connect everything together.

First, we need to implement simple pure validation functions according to our requirements.

Let’s redefine Email method in Forms object to use new Email input component with validators and input config.

According to our requirements, the validation is triggered when a user tries to send a form or when they touch the field. The caller (RegisterForm method) has the state of the whole form and knows when the form is sent. The Email method knows when the e-mail input field was touched.

When one of these two Signals is triggered with a true value, the form should be validated, but only when there is anything to validate. To achieve that, we will combine $email.signal , $formSend , and $emailTouched.signal and return the validation result if any of them is true. This composition will block the validation until the user makes any action, we don’t want to show errors on the not touched form, right?

There is also a contramap method which can be new for you. In simple words, this function will convert a value from $email into $emailObserver and return None when the e-mail value is invalid.

The docs will describe it perfectly, so I’ll just leave it as a reference.

Creates another Observer such that calling its onNext will call this observer’s onNext with the value processed by the project function.
This is useful when you need to pass down an Observer[A] to a child component which should not know anything about the type A, but both child and parent know about type B, and the parent knows how to translate B into A.
def contramap[B](project: B => A): Observer[B]

In my opinion, this code looks terrible! We can’t really extend the library API, so let’s make it cleaner with some fancy Scala extension methods!
Please notice how we used the Observer.combine function to combine Observers that listen to our e-mail value changes.

Moving back to the above code.
First of all, let’s get rid of this terrible contramap. We intend to emit a value ONLY when the isEmailValid function returns None (which means the email was valid and no error was returned).

Using that vocabulary, we can create a high-level API. $emailObserver.emitIfValid(isEmailValid) would look pretty nice.
$emailObserver is of type Observer[Option[A]], we accept the validator of type A => Option[A] to finally apply contramap.

Now the Email looks like this:

The code looks much better, but we still have this ugly part related to input validation.

This piece of code inside mapmixes two things.
First, applying a boolean operation. Second, filtering out the validation result when none of them is true.

With two simple implicit conversions, we can extend the Signal to create a nice and lean API.

The final Email looks like this

Let’s move on to two password fields:

A thing worth noticing is that the second password validation is dependant on the first password value. That’s why $password.signal is combined with $secondPassword.signal before doPasswordsMatch validation. The rest of this code is very similar to Email, so I’m leaving the deeper analysis to you.

Last but not least is the top-level RegisterForm method that’s holding the whole form and its pieces.

Let’s define the form state case class:

Now the RegisterForm:

$formState.signal.foreach(println)(L.unsafeWindowOwner) is used only to print the results to the console because we don’t really do anything useful with that data right now.
RegisterForm keeps a hand on the whole state and form send status.
An interesting thing is that updater can create an observable that changes the value kept inside Var.

Can we do better? Of course we can!
Using the monocle (https://www.optics.dev/Monocle/) library, we can utilize Lens to get rid of copy methods.

Having FormState case class annotated with @Lenses:

And the helper implicit method:

We get the final result:

Final thoughts

Laminar is a bare-bones library. It’s like a double-edged sword. On one hand, it gives you A LOOOT of freedom in how you wanna approach the code structure and design. On the other hand, you need to do more initial coding to make abstractions that suit your problem.

Of course, the abstraction I described here is just my own vision of how I would use that library.

It was really cool to discover these method extensions that created mini DSL.
I also had a hard time getting used to the reactive style and syntactic sugar that Laminar provides, but once you get used to them, it’s pretty straightforward.

In the end, I think I really like the freedom Laminar gives to me.

--

--