Creating an Angular.js application without JavaScript: Scala.js
In my day job I work a lot with Angular.js. And while this is a great framework, sometimes I do miss the type-safety and advanced features of languages such as Scala. Getting “undefined is not a function” is not the the most informative error message you get when developing JavaScript applications.
A couple of weeks ago I ran across a twitter message showing that Scala.js 0.60 (http://www.scala-js.org/) was released. With Scala.js you can compile Scala directly to JavaScript. So you can use all the advanced library and language features of Scala, and create JavaScript applications in a typesafe, functional manner!
So for this article I’ll show you the steps I went through to create a minimal Angular.js application using nothing but Scala (and some HTML templates of course).
http://www.smartjava.org/examples/scalajs/html/index-dev.html#/home
This Angular / Scala.js app will show the following features:
- Controllers written in Scala
- Directives written in Scala
- Filters written in Scala
- Use the Angular.js route module for handling URL paths
- Use foundation for templating
You can find the complete sources for this application in GitHub (https://github.com/josdirksen/smartjava/tree/master/scalajs).
Lets get started
The first thing we need to do is make sure we have node.js installed. This isn’t really necessary but will speed up development considerably. So install node.js from here (https://nodejs.org/download/) before you continue. To work with Scala.js we have to install an sbt plugin. In the file plugins.sbt in the project directory add the following line:
addSbtPlugin('org.scala-js' % 'sbt-scalajs' % '0.6.0')
Note that I’ve tested this with sbt 0.13.7, so make sure you’ve got a current version of sbt installed. Next lets look at the build.sbt file we’ve used for this project:
enablePlugins(ScalaJSPlugin) name := "scalajs" version := "1.0" scalaVersion := "2.11.5" libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "0.8.0" libraryDependencies += "com.greencatsoft" %%% "scalajs-angular" % "0.4-SNAPSHOT" resolvers += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots" jsDependencies += "org.webjars" % "angularjs" % "1.3.14" / "angular-route.js" dependsOn "angular.js" jsDependencies += "org.webjars" % "angularjs" % "1.3.14" / "angular.js" jsDependencies += "org.webjars" % "angular-foundation" % "0.3.0" / "mm-foundation.js" jsDependencies += "org.webjars" % "angular-foundation" % "0.3.0" / "mm-foundation-tpls.js" persistLauncher in Compile := true persistLauncher in Test := false skip in packageJSDependencies := false
In this build.sbt you can see the following:
- We enable the scalaJS Plugin
- We include a couple of Scala.js dependencies like we normally do for scala
- We also include a number of jsDependencies. These are the JavaScript libraries we want to use in our web application
- And finally we configure how the application will be packaged. More on that later
Lets look a bit at the last couple of lines of this build file:
persistLauncher in Compile := true skip in packageJSDependencies := false
By setting the persistLauncher to true, we tell Scala.js to create the JavaScript that will automatically launch our application and by setting skip in packageJSDependencies to false, we tell Scala.js to package all our external JavaScript files into one big library for easy inclusion in your HTML page.
Before we look at the Scala code and the HTML templates lets first look at a couple of useful SBT commands. Go to the directory where your project is and run sbt to open the sbt console:
jos@Joss-MacBook-Pro.local:~/git/scalajs$ sbt [info] Set current project to scalajs (in build file:/Users/jos/git/scalajs/)
Now to generate the JavaScript code, we first have to enable node.js so that creating the JavaScript goes a whole lot quicker. In the sbt console set the following:
> set scalaJSStage in Global := FastOptStage [info] Defining */*:scalaJSStage [info] The new value will be used by compile:jsEnv, compile:scalaJSExecClasspath and 1 others. [info] Run `last` for details. [info] Reapplying settings... [info] Set current project to scalajs (in build file:/Users/jos/git/scalajs/) >
Now you can compile the Scala to JavaScript by calling fastOptJS
> fastOptJS [warn] The global sbt directory is now versioned and is located at /Users/jos/.sbt/0.13. [warn] You are seeing this warning because there is global configuration in /Users/jos/.sbt but not in /Users/jos/.sbt/0.13. [warn] The global sbt directory may be changed via the sbt.global.base system property. [info] Fast optimizing /Users/jos/git/scalajs/target/scala-2.11/scalajs-fastopt.js [success] Total time: 7 s, completed Mar 8, 2015 8:01:31 AM
Enough about SBT, lets look at the Scala code
Angular App in Scala
The complete file can be found in Git (https://github.com/josdirksen/smartjava/tree/master/scalajs) so we won’t show that here. We’ll just look at the interesting parts. Before we look at the JavaScript though, lets look at the HTML templates:
<!DOCTYPE html> <html ng-app="helloworld"> <head> <meta charset="UTF-8"> <title>Minimal angular.js</title> <link rel="stylesheet" href="css/foundation.css" /> <style> .tile { background: #ffeeee; } </style> </head> <body> <div class="row"> <div class="large-12 columns"> <h1>Sample angular.js / foundation / scala.js app</h1> </div> </div> <div ng-view></div> <!-- Include Scala.js compiled code --> <script type="text/javascript" src="../../../target/scala-2.11/scalajs-fastopt.js"></script> <script type="text/javascript" src="../../../target/scala-2.11/scalajs-jsdeps.js"></script> <script type="text/javascript" src="../../../target/scala-2.11/scalajs-launcher.js"></script> </body> </html>
This file is our main index file. As you can see, not that special. Just a single div where we have an ng-view. What is interesting are the three JavaScript files at the bottom of the page. These are generated by Scala.js and contain all our code:
- scalajs-fastopt.js: our compiled application.
- scalajs-jsdeps.js: all dependencies as a single file
- scalajs-launcher.js: code that will launch our application when the page is loaded
We have two additional HTML files, one that is shown as the main page in the ng-view we just saw, and one that is used as the tiles you saw in the beginning of this article:
<!DOCTYPE html> <html ng-app="helloworld"> <head> <meta charset="UTF-8"> <title>Minimal angular.js</title> <link rel="stylesheet" href="css/foundation.css" /> <style> .tile { background: #ffeeee; } </style> </head> <body> <div class="row"> <div class="large-12 columns"> <h1>Sample angular.js / foundation / scala.js app</h1> </div> </div> <div ng-view></div> <!-- Include Scala.js compiled code --> <script type="text/javascript" src="../../../target/scala-2.11/scalajs-fastopt.js"></script> <script type="text/javascript" src="../../../target/scala-2.11/scalajs-jsdeps.js"></script> <script type="text/javascript" src="../../../target/scala-2.11/scalajs-launcher.js"></script> </body> </html>
<p simple-directive="some configuration"></p>
So now that we’ve seen the HTML, it is time for the actual code. The first part we’ll look at is the starting point of any Angular application, the module configuration:
object HelloWorld extends JSApp { def main(): Unit = { val module = Angular.module("helloworld",Seq("ngRoute","mm.foundation")) module.controller(HomeController) module.config(RoutingConfig) module.filter(ToUpperFilter) module.filter(IntDoublerFilter) module.directive(SimpleDirective) } }
Here we define our Angular module (named helloworld) and add two dependencies: ngRoute and mm.foundation. ngRoute provides us with a way to easily configure URL mapping to templates and controllers, and mm.foundation (https://github.com/pineconellc/angular-foundation/tree/gh-pages) provides us with directives that map to foundation (http://foundation.zurb.com/) widgets.
Next we link to the controllers, filters, directives, config objects we want to use in this app. Note that these are all statically typed, so you can’t forget or register angular components incorrectly. In this example we have five additional components, lets look at each one. First lets look at the RoutingConfig:
object RoutingConfig extends Config { @inject var routeProvider: RouteProvider = _ override def initialize() { routeProvider .when("/home", Route(HomeController)) } }
As you can see, this is very easy and pretty much the same as you’d do it in JavaScript. The main difference is that instead of providing string values in JavaScript we now just reference a specific controller to use for a path.This controller, which we registered earlier, looks like this:
object HomeController extends PageController { import js.JSConverters._ val templateUrl = "templates/home.html" override type ScopeType = ControllerData @inject var service: Interval = _ override def initialize(scope: ScopeType): Unit = { scope.title = "This is the title" scope.subTitle = "Make Me Big" scope.anotherScope = 1 to 12 toJSArray service.apply( () => scope.count = System.currentTimeMillis() % 10000, 100) } /** * The specific scope data used in this controller */ trait ControllerData extends Scope { var title: String = js.native var subTitle: String = js.native var count: Double = js.native var anotherScope: js.Array[Int] = js.native } }
I won’t go into too much detail here, since the code is pretty self-explanatory. What you can see here is that we define the template that we want to show (when the url from the route is accessed), we setup some scope variables (all typed!) and start an interval using the injected Interval service.
If you’ve looked through the pages you can also see we use a number of filters. Defining a filter in Scala.js is really easy:
/** * Simple filter: text to uppercase */ object ToUpperFilter extends Filter { override val name = "upper" override def filter(text: String): String = text.toUpperCase } /** * Simple filter: double a value */ object IntDoublerFilter extends Filter { override val name = "double" override def filter(text: String): String = text.toInt*2 toString }
And finally we have a directive which we use in tile.html.
object SimpleDirective extends AttributeDirective { override val name = "simpleDirective" @JSExportAll case class Address(ip : String) override def link(scope: ScopeType, elems: Seq[Element], attrs: Attributes, controller: Controller*) = { val elem = elems.head.asInstanceOf[HTMLElement] elem.textContent = "Some content set from the directive" } }
When you compile this application to JavaScript using sbt and open the HTML pages you’ll get what we showed in the beginning. A very basic Angular.js application with custom filters, directives and two Angular extensions.
Reference: | Creating an Angular.js application without JavaScript: Scala.js from our WCG partner Jos Dirksen at the Smart Java blog. |