Categories
scala.js series

Part 1: Using akka and react to organize your single page scala.js application

This blog should resonate specially to those users who are looking for something like Redux to organize a single page React+Scala.js application.

We will try to manage a single page application from a single nested case class. All updates to this model will be delegated to an AKKA actor. Later in this blog, we will be using Monocle to simplify the update of the application model.

The way we are going to do this is by having two separate buttons. Each button will show an integer that will be incremented with a click.

We will start very simple, and as we progress, we will enhance our code.

Project setup

We will start simple from the react slinky template.

% sbt new shadaj/create-react-scala-app.g8

For the sake of consistency, please keep the default values. After running command dev under sbt you should see the following browser content if everything goes well:

~/projects/my-app % sbt dev 

Now we add the akka dependencies to your build.sbt:

// build.sbt
libraryDependencies += "org.akka-js" %%% "akkajsactor" % "2.2.6.5"
libraryDependencies += "org.akka-js" %%% "akkajsactortyped" % "2.2.6.5"

At this point, we are ready for coding!

We then setup the application model

We will be creating a package called “models” under our default package “hello.world”. We then create file Application.scala with the following code:

// File Application.scala
package hello.world.models

case class Counter(value: Integer = 1)

case class ApplicationContainer(var app: Application = Application())

case class Application
(
  counter1: Counter = Counter(),
  counter2: Counter = Counter()
)

That is it for our application model. We keep it simple.

Next we create a package file under “models” . We use this package to retrieve and initialize our application model:

//package.scala
package hello.world

package object models {
  val topModel = ApplicationContainer()
}

We then render our buttons showing the counter values

Now we add our buttons. At this point, they won’t do anything at all. We edit file App.scala and add 2 html buttons inside method render():

// File App.scala
....
..
def render() = {

    div(className := "App")(
      header(className := "App-header")(
        img(src := ReactLogo.asInstanceOf[String], className := "App-logo", alt := "logo"),
        h1(className := "App-title")("Welcome to React (with Scala.js!)")
      ),
      p(className := "App-intro")(
        "To get started, edit ", code("App.scala"), " and save to reload."
      ),
// add the following: start
      button(
        s"Click to increment counter1 ${models.topModel.app.counter1.value}"
      ),
      br(),br(),
      button(
        s"Click to increment counter2 ${models.topModel.app.counter2.value}"
      )
// end of additions
    )
  }

At this point, you should see two additional, non functional buttons, available in the browser:

One ugly hack is required to make this work, just one

In order for the counters to increment, we need to tell the react application to update. For this to work, we need to access react method forceUpdate() externally from an akka actor. This is done by modifying the following in App.scala:

// File App.scala
....
// Added ApplicationProxy for external access of forceUpdate()
object ApplicationProxy {
  var update:() => Unit = () => ()
}

@react class App extends StatelessComponent {
  type Props = Unit
  private val css = AppCSS

// Added componentWillMount() to "steal" forceUpdate() out of react
  override def componentWillMount() = {
    ApplicationProxy.update = () => {
      this.forceUpdate()
    }
  }

  def render() = {
.....

Enter the awesomeness of akka

Akka deals with messages. This means we first need to build our message types before we do anything. Since we only increment two counters, we are going to have two message names, IncrementCounter1 and IncrementCounter2. We create package “messaging” under root package “hello.world” and then create file ApplicationMessageListContainer.scala:

// File ApplicationMessageListContainer.scala
package hello.world.messaging

trait ApplicationFrontEndMessage

object ApplicationMessageListContainer {
  object IncrementCounter1 extends ApplicationFrontEndMessage
  object IncrementCounter2 extends ApplicationFrontEndMessage
}

Now we are ready to create the messaging code. Under same package “hello.world.messaging” we create file MessageHandler.scala:

// File MessageHandler.scala
package hello.world.messaging

import akka.actor.{Actor, ActorLogging, ActorSystem, Props}
import hello.world.messaging.ApplicationMessageListContainer.{IncrementCounter1, IncrementCounter2}
import hello.world.{ApplicationProxy, models}
import hello.world.models.{Application, Counter}

object MessageHandler {

  val system = ActorSystem("ApplicationStoreActorSystem")

  val actor = system.actorOf(Props[ApplicationActor], name = "ApplicationInfoActor")

  class ApplicationActor extends Actor with ActorLogging {
    def receive = {
      case m: ApplicationFrontEndMessage => {
        models.topModel.app = messageUpdate(m)
        ApplicationProxy.update()
      }
    }

    private def messageUpdate: PartialFunction[Any, Application] = {
      case IncrementCounter1 =>
        log.info(s"Increment  counter1")
//monocle will improve this ugly update
        models.topModel.app.copy(counter1 = Counter(models.topModel.app.counter1.value + 1))


      case IncrementCounter2 =>
        log.info(s"Increment counter2")
//monocle will improve this ugly update
        models.topModel.app.copy(counter2 = Counter(models.topModel.app.counter2.value + 1))
    }
  }

}

The messaging system is basically copy-modifying the current model. With a 2 level case class, it already looks pretty ugly. We will be using monocle to address this ugliness in my next blog.

Of course, we need to add a listener to our buttons (onClick) in file App.scala for the buttons to trigger changes:

// File App.scala
package hello.world

import hello.world.messaging.MessageHandler
import hello.world.messaging.ApplicationMessageListContainer._
import slinky.core._
import slinky.core.annotations.react
import slinky.web.html._
.......
....
  def render() = {
    div(className := "App")(
      header(className := "App-header")(
        img(src := ReactLogo.asInstanceOf[String], className := "App-logo", alt := "logo"),
        h1(className := "App-title")("Welcome to React (with Scala.js!)")
      ),
      p(className := "App-intro")(
        "To get started, edit ", code("App.scala"), " and save to reload."
      ),
      button(
        s"Click to increment counter1 ${models.topModel.app.counter1.value}",
//onClick added
        onClick := (_ => {
          MessageHandler.actor ! IncrementCounter1
        })
      ),
      br(),br(),
      button(
        s"Click to increment counter2 ${models.topModel.app.counter2.value}",
//onClick added
        onClick := (_ => {
          MessageHandler.actor ! IncrementCounter2
        })
      )
    )
  }

Now take the app for a ride and click on those buttons, they should increment as you click on them:

Not too bad isn’t it? Now you have a single page app with a model organized by akka messaging. In my next blog, we will add monocle to manage our application model.

Code for this blog can be accessed through the following repo:

https://github.com/scala-blog/akka-react-no-monocle/tree/master

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s