Laminar 102: A reactive “Hello, world” example

In my first Laminar tutorial I showed how to set up a Scala sbt project to use Laminar, and then showed a “static” example — i.e., there were no moving parts. Please see that tutorial first if you’ve never used Laminar.

BUT, because Laminar is meant for writing reactive applications with observables and observers, this tutorial begins to show its reactive concepts.

Please note that this example is almost 100% the same as the Laminar “Hello, world” example, except that I add a few minor things to it.

Just like my first tutorial, I also provide an sbt project for this tutorial to help you get up and running asap:

Clone that project if you want to follow along with the text below.

A Laminar reactive “Hello, world” example

Given that introduction, this is what this example looks like in a browser:

A Laminar 101 Hello, World reactive example

What happens in this example is that you type into the input field, and then whatever you type is reactively sent to the two output fields that are below the input field. The second field transforms whatever you type into uppercase.

The Laminar/Scala/Scala.js code

Here’s the source code that makes this example work:

import com.raquo.laminar.api.L._
import org.scalajs.dom

object Laminar101Reactive {

    def main(args: Array[String]): Unit = {

        val nameVar = Var(initial = "world")

        // an outer “wrapper” div
        val rootElement = div(
            padding("2em"),
            div(
                backgroundColor("#e0e0e0"),
                label("Your name: "),
                input(
                    onMountFocus,
                    placeholder := "Enter your name here",
                    // as the user types into this 'input' field, send the contents
                    // of the input field to nameVar. nameVar is a reactive variable
                    // (observable), and it’s used to update the two DIVs below.
                    inContext { thisNode => onInput.map(_ => thisNode.ref.value) --> nameVar }
                ),
            ),
            // a div for the first output area
            div(
                backgroundColor("#d9d9d9"),
                "Reactive 1: ",
                // any time nameVar is updated, it sends us a signal, and this
                // field is updated:
                child.text <-- nameVar.signal
            ),
            // a div for the second output area
            div(
                backgroundColor("#d0d0d0"),
                // any time nameVar is updated, it sends us a signal, and this
                // field is updated. note in this case that the value is map’d:
                "Reactive 2: ",
                child.text <-- nameVar.signal.map(_.toUpperCase)
            )
        )

        // `#root` must match the `id` in index.html
        val containerNode = dom.document.querySelector("#root")

        // render the element in the container
        render(containerNode, rootElement)
    }

}

As you can see, the code consists of several div elements, with different content in each element. Hopefully the HTML-ish elements make sense, so I won’t describe them. But here are a few notes about the unfamiliar parts:

  • From the Laminar docs, onMountFocus “focuses the element when it's mounted into the DOM. It’s like the HTML autoFocus attribute that actually works for dynamic content.”
  • inContext is a way to get a reference to the node that you’re inside. In this example, inContext refers to the input field. Its code may be a little easier to read like this:
inContext { thisNode => 
    onInput.map(_ => thisNode.ref.value) --> nameVar 
}

Discussion

As I write in my book, Functional Programming, Simplified, inContext is either (a) a class that takes a function parameter, or (b) a function that takes a by-name parameter. In this case, the truth is that I don’t know what inContext is — I haven’t taken the time to look it up.

But what this code does is that whenever a user types something into the input field, an onInput event is triggered, and that causes the map method to be run. Inside map the value we get from onInput is ignored with the _ character, and we just yield thisNode.ref.value, which is the value in the input field. That value is then sent “to” the nameVar reactive variable.

Because nameVar is an observable variable and it’s observed by these two lines of code:

child.text <-- nameVar.signal
child.text <-- nameVar.signal.map(_.toUpperCase)

those two lines are immediately updated.

That’s the way observables and observers work,
and Laminar is 100% based on this principle and
style of coding.

Here’s another way of looking at how that code works:

The Laminar 102 code

An awesome thing about Laminar is that you can write Scala code that’s compiled to JavaScript (using Scala.js and sbt), letting you write single-page applications using Scala!

A note about Scala

One thing to mention is that in this line of code, where it looks like child.text is receiving a signal:

child.text <-- nameVar.signal

the <-- thing is just a method. So that line of code is the equivalent of this:

child.text.<--(nameVar.signal)

But because of Scala’s mojo, you can use the first approach.

Other notes

As you’ll see in the Github project:

  • The build.sbt file is almost the same as my first tutorial
  • The index.html file is almost the same as my first tutorial
  • You compile your Scala code to JavaScript using this command in sbt:
sbt:Laminar101> ~fastOptJS

All of that is explained in my first tutorial.

One thing that isn’t mentioned in that tutorial is that whenever you update your Scala code and it’s recompiled into JavaScript, you need to refresh your browser so it picks up the newly-generated JavaScript file.

Experiment with the examples

Now that you’ve seen this example and have access to my Github project, you can also copy and paste the other Laminar examples into this project to experiment with them.

The next tutorial

When you’re ready for the next step, I just added my third Laminar tutorial, A small Laminar reactive routing example.

Resources

If you go forward in working with Laminar, here are some helpful resources:

Laminar depends on the following three projects, and if you go forward, their docs will also be helpful: