If you don’t know Angular, it’s a frontend web framework developed by Google in TypeScript, similar to React or Vue. It is a component based framework, and to each component is associated a TypeScript class aiming to control what the HTML component must do. These classes can use services. A service is simply another (usually global) instance of a TypeScript class, either with plenty of facility methods (for example for making http calls), or with global object used to pass information from one component to another.
TLDR: If you want to jump right into the action, you can head over the accompanying repo. Commits in the repo follow along this article. The master branch shows the final version.
Setup the project
In order for everything to work properly, we simply need a bunch of plugins for managing the project. There are basically three things that we need to do: telling TypeScript what are the types that we are going to provide it, manage the npm dependencies that we want to use, and tell Scala what are the types that exist in JS/TS in the dependencies that we use. Luckily for us, there are exactly three plugins to do just that, to be added inside `project/plugins.sbt`:
addCompilerPlugin(“org.scalameta” % “semanticdb-scalac” % “4.3.10” cross CrossVersion.full)
scalacOptions += “-Yrangepos”/** Explicitly adding dependency on Scala.js */
addSbtPlugin(“org.scala-js” % “sbt-scalajs” % “1.1.1”)/** Plugin for generating TypeScript declaration file. */
resolvers += Resolver.jcenterRepo
addSbtPlugin(“eu.swdev” % “sbt-scala-ts” % “0.9”)/** Plugin for generating Scala.js facades from TypeScript declaration file. */
resolvers += Resolver.bintrayRepo(“oyvindberg”, “converter”)
addSbtPlugin(“org.scalablytyped.converter” % “sbt-converter” % “1.0.0-beta18”)/** Plugin for managing npm dependencies. */
addSbtPlugin(“ch.epfl.scala” % “sbt-scalajs-bundler” % “0.18.0”)
And now we can simply enable all of these, together with some barebones configuration, in our `build.sbt`:
name := “AngularServices”
version := “0.1”
scalaVersion := “2.13.2”/** npm module will have version “0.1.0” */
scalaTsModuleVersion := (_ + “.0”)/** Enabling ScalaJS */
enablePlugins(ScalaJSPlugin)/** Enabling ScalaTS */
enablePlugins(ScalaTsPlugin)/** Enabling Scalably typed, with scala-js-bundler */
We are all set to start creating a JS module for Angular.
A first service
One of the strong suit of Scala is its standard collection library. For example, the native JS Array api has no method for taking distinct elements in the array. Let us fix that. Here is a simple implementation which does that (to be put into
target/scala-2.13/scalajs-bundler/main/angularservices-fastopt.d.ts and you’ll see … nothing! That’s right, because we didn’t actually tell Scala to export this class, nor its members, to the JS world. This is easily done by adding the two annotations to the class:
Issuing the command again, the content of the declaration file is now
Note: the careful reader maybe asked himself why the type of
f in the
distinctBy method was the weird looking
js.Function1[A, B]instead of
A => B. This is because the type
A => Bis a Scala function, and pure Scala objects do not enter the JS world. Hence, if we had asked for
f: A => B, it would have been impossible for JS to give us the correct argument.
Adding the Angular project
Until now, we didn’t do anything specifically related to Angular. Let us do that now. We are going to add an Angular project within the Scala project directory, into `webapp` directory. To that end, we go to `webapp` directory and issue (outside sbt), the command
ng new FromScalaWithLove(I chose not to add angular routing and I chose CSS for styling, as we won’t use that.) We can safely delete the content of the
app.component.html file, and replace it with
<h1>From Scala, with love</h1>
ng servein the Angular project
FromScalaWithLove, and heading to
localhost:4200 should make this title appear.
Using our Scala service
The first thing is to copy paste the files in
webapp/FromScalaWithLove/node_modules/scala-module. That will make our compiled JS code, together with the type definitions, available to TypeScript. And in
app.component.ts, we can add the lines
Saving the file should make Angular recompile and refresh the page. Ew, doing this gratified us with an unfriendly `ERROR NullInjectorError`. This makes sense because we never tell the Dependency Injection (DI) mechanism of Angular to take care of our class. This is easily fixed by adding the
ArrayEnhanced type inside the
providers array in the
app.module.ts file. Saving again should make the error disappear.
We can now happily use the
ArrayEnhanced service, for example by adding these lines inside the constructor of the
Polishing the dev experience
Having to copy-paste the compiled files inside Angular node modules is a tiny bit annoying. We want to automate this process. This is simply done by adding the following lines to the
After reloading sbt, we can issue the
makeModule command, which will copy-paste everything properly. Note that in an actual setup, we would like something a bit more fine tuned than hard-coded paths. For now, however, it will do.
The Angular ecosystem makes heavy use of the FRP library RxJS. An Angular user will then most likely expect a service (with asynchronous behaviour) to return Observables instead of, for example, promises. In order to do that, we once again need to change the build.sbt file, in order to add the npm dependency that we want. In this case, `rxjs`. For the sake of speed, we will also tell our project to use yarn instead of npm. This is done by adding the following lines:
Compile / npmDependencies ++= Seq(“rxjs” -> “6.4.0”)
useYarn := true
(You might want to
clean first in sbt in order to avoid some weird shenanigans.) Now you can
reload and kick ScalablyTyped off by issuing
compile. This is going to take quite some time (probably 2 to 5 minutes), because all the TypeScript definitions have to be compiled into Scala.js facades. But don’t worry, this is only a one time process.
We can now create a Scala class which will expose an Rx observable for Angular to use. Let us start with something very modest:
makeModule command should now create a declaration file containing (among others)
Since this command automatically copy-paste into Angular’s node module directory, the shiny new
EmitRxObservable service will be available immediately. Don’t forget to register it in the app module providers, though.
Note: you will likely hit the following error:
ERROR in node_modules/scala-module/angularservices-fastopt.d.ts(9,7): error TS1086: An accessor cannot be declared in an ambient context.
which comes from the fact that the interfaced exposed our Scala
def as a
get accessor. This is “solved” by adding the compilerOption `”skipLibCheck”: true` in `tsconfig.json`. I am not a TypeScript aficionado, so there is perhaps something better to do…
We can now happily consume our service by adding the
emitRx: EmitRxObservable to the constructor of the
app-component, and for example the line
emitRx.naturalNumbers.forEach((e) => console.log(e));
in the constructor. Upon reload, the console should show the natural numbers getting printed.
More Scala, please!
We can define a model
User, to be used by TypeScript in the project, directly from Scala. This will be a case class with its member exported:
We add a facility method
maybeDateOfBirth to be used inside Scala, as
Option[A] is more Scala friendly than
A | undefined. However, even if this method will exist in TS (because we export all members), it won’t be usable since an Option is a pure Scala type, hence opaque. More precisely, it will be usable but TS will not be able to do anything with the returned object, except carrying it along and possibly pass it back to a Scala.js function. But TypeScript will be able to create instances of
User (since it knows all the types of the constructor), and we can therefore ask for them in a Scala service. One example is done below:
And from TS, this can be used by doing
This shows a tiny bit of what is possible doing Scala: we can define models, let TypeScript instantiate them, and use them as regular Scala objects. The only thing that we need to be careful about is to ask from, and return to, TypeScript, only stuff that it understands.
Let’s go crazy
Scala also has a gigantic ecosystem with high quality libraries. There is no reason we shouldn’t use them for our Angular projects!
Let us imagine this use case: you are creating a dashboard of some sort, and you need to download a certain amount of data. You don’t want to download everything at once. You are then given a list of indices
[0, 1, …, n-1]and you need to make a call for each of these. However, you know that some of them will take longer than others to be processed by your backend, so you don’t want to have these guys be a bottleneck. Also, sometimes your calls fail for some reason (not that often, but it happens) and in these cases you would like to retry twice with some back-off.
Ultimately, this is what you want to do:
- make n http calls to your backend,
- always 3 of them concurrently
- retry twice those who fail,
- if, despite the two retries, one call still fail, the whole process should fail,
- be able to track the progress,
- return an observable which will emit once an array with the result of the n calls, in such a way that the j-th element is the result of mapping j, and
- give the user the possibility to cancel the process before completion.
Good news, this is going to be piece of cake!
We are going to use the ZIO library for that. Other good choices could be AKKA stream or Monix. The first thing to do is to add the ZIO dependency to our project. Add the following line to the build.sbt file:
libraryDependencies += “dev.zio” %%% “zio” % “1.0.0-RC21–2”
We will also need the implementation of the comprehensive
java.time library for Scala.js, available via
libraryDependencies += “io.github.cquiroz” %%% “scala-java-time” % “2.0.0"
The function that we are going to expose to TypeScript will have the following signature:
program argument is thought of as an asynchronous observable emitting only once an element of type
U. We could also ask for a function returning a
js.Promise. We choose the
Observable type because it is the one returned by Angular’s `HttpClient`, for example. Note that we only need to export the members of the
CompletionState class, because TypeScript never needs to create an instance. It is only required that it understands the ones we are going to give back.
From Rx Observable to ZIO effect
We need to turn this
program function into a ZIO effect that we are going to use afterwards. The program assumes that the returned observable might fail, so we need to take that into account. ZIO has us covered and has the function
effectAsync to do just that:
This function thus lifts an observable returning a
U into a ZIO effect that might fail with a
js.Error, and might succeed with a
U. Note that you could very well preserve the fact that in TypeScript, an error can really be “anything”. In that case, we would have asked the ZIO effect to fail with a
js.Any instead of
The retry policy
We decided to allow each program to fail a certain number of times. In ZIO, you need to provide a “retry policy” describing the rules to follow in the retry. We can build a retry policy that fits our needs by doing
If the reader is not familiar with ZIO and wonders why this thing does what we want, they can head over here.
The global execution plan
The last ZIO piece is a pure function taking the inputs from TS, and a bunch of small helper ZIO effect that are going to be actually built by using Rx Observables. Here is the function
program argument is the program provided by TS, lifted to ZIO. The
nextProgress effect will notify that a new program has finished. The two effects
fail happen at the end, the former when the whole thing succeeds and the latter when it fails. As we see, the implementation is pretty straightforward. The funny symbol
<* means that the right effect will be executed after the left one, but its result will be discarded (similar to Rx
The bridge to JS world
And that is all. The nice thing that we get from this is that our
execution function is pure and can easily be tested. The Scala compiler is also able to ensure us that the global program will never fail. That means that, from TS’ side, we can be certain that the only errors happening will be the ones coming from the input programs, or the
TaskCancelledError in the event that TS cancels it.
We can now use our powerful function from Angular. The interested reader will find in the repo an integration with Angular UI. Below, we simply mention a usage with the console.
Let us write a “dummy” program simulating an asynchronous computation:
This program returns the input one second later, failing with probability 0.1. It prints the input in case of success, and warn “boom” the input in case of failure. Injecting an instance of
ZIOService in our component, we can for example use our function like this:
You will be able to see that
- elements get printed 3 by 3
- the progress will be displayed accordingly
- from time to time, a “boom j” will be displayed, and you will see that the value will be printed after bigger numbers
- the result array printed at the end is well ordered, as expected.
If you want to see the cancellation in action, you can for example do
I hope this example demonstrated that Scala, with the help of ZIO, can give you an enormous amount of power via a straightforward interface. It’s now time to draw conclusions from all of this.
Why and when do this?
Why should one make Angular Services in Scala? I believe there are a variety of good reasons that I try to discuss below:
- your backend is in Scala. In that case, you will be able to define all your business models for the backend, and expose them to JS/TS to be used immediately. And you can write, in Scala, the type-safe versions of the http calls that you want to make to your backend endpoints. That way, all of your models will be completely in sync, you will be able to have an efficient Scala-to-Scala communication (the ScalaTs repo actually has an example of that).
- you need to make very advanced stuff, like above. Using ZIO is only one possible example. But Scala has a lot to offer and is perfectly suited to model complicated business domains.
- you want to go “all in” and make all your services in Scala, leaving to TypeScript and Angular only the responsibility of the controllers. That way, you can have a nice and clean Scala project, exposing just the right amount of information to your components. You are forced (in a good way) to keep a clear separation of concerns between components and services
- testing your services will be a lot easier. Scala has amazing test libraries that will allow you to extensively test your services, mocking their concrete implementations if need be.
There is no such thing as a silver bullet in computer science. This technology is no exception. I can see at least three “drawbacks” that I personally think should not keep you away from choosing it, but you should be aware that they exist.
- scalably typed typings: scalably typed generates Scala.js facades from TS types for you. Given the nature of TypeScript, they are sometimes a bit cumbersome to work with. If you happen to need a fine tuned facade for one of your libraries, it might be worth writing them by hand. It’s really not that hard
- The ScalaTs plugin is young: the following months, perhaps you will find some very advanced use case that the plugin is not able to handle. No worries, you can still write things down by hand, and raise an issue!
We did not cover using Angular-Angular services within our Scala services, but it is certainly possible to do so.
Don’t hesitate to give it a try! It is easy to get working with and, who knows, it can be a nice entry point for you into Scala…