For a few recent projects, I've been using Angular.js at the client and the Scala Play framework at the server.
I also have a handy library that helps make a lot of things quick, easy, and tidy.
Normally, every framework has its good points but the occasional gotcha. But in this case, I found them to be a very virtuous combination, each making the others more useful.
Play and Handy
Play is a non-blocking framework, and with my handy library, it's nice and easy to write code that can include as many asynchronous calls as it likes and returns the result to the client. Even if the JSON conversion involves more asynchronous calls to fetch other information you'd like to include.
def get(contentId:String) = DataAction.one {
implicit request =>
for (
content <- contentRef(contentId);
approved <- request.approval ask
Permissions.ReadContent(content.itself)
) yield content
}
In our hypothetical example here, DataAction.one
will call content.toJsonFor(request.approval)
to produce JSON to return. In our hypothetical example the "content" is sometimes a sequence — a playlist of other pieces of content to show in order — and converting it to JSON fetches and embeds them in the response. And it all happens aynschronously in a compact monadic style.
(This example is a slightly butchered version of what happens with content sequences in Impressory)
I'm gradually improving the documentation at handy, if you want to find out how it makes things easier.
Play's snag with HTML
If you want to Play's templating system for HTML, it has the annoying gotcha that while you can embed an Option straight into the template,
<span>@{object.myOptionValue}</span>
you can't embed a Future straight into the template.
<!-- not supported -->
<span>@object.myFutureValue</span>
Of course that's where "gotcha" means "has the same limitation that every other templating system on the planet has too".
Play's templating, like just about every other templating framework I know of, doesn't have the smarts to turn the template itself into a Future if it's got a Futures embedded into it.
This is perfectly fair enough, but means that if you're working in a non-blocking way, you end up needing to resolve all your Futures and only then call the template, passing them in as explicit parameters.
Which always made me grumpy, as it was unnecessary overhead making things look messy. Thanks to my handy library, I could convert to JSON in an asynchronous way, with embedded Futures in the conversion, but not to HTML.
Angular.js to the rescue
The obvious way that Angular.js helps Play is that the templating happens on the client. You're no longer embedding data into the HTML, so you don't end up needing to resolve those futures.
A problem not encountered is as good as a problem solved!
A snag with Angular.js
The first gripe I had about Angular.js was perhaps an unusual one. Not the documentation (that I've coped ok with), but that inlining reusable snippets is a little clunky.
There are directives, and there's ngInclude
, but the template to include either has to either:
- be fetched in another http request,
- or embedded into a
<script>
element in the HTML. (Which doesn't itself solve how to split it into a different file.)
Play's templating to the rescue
Well, this is of course is where Play's templating solves the problem.
An Angular.js template can be broken up into as many fragments as you like, and pieced together on the server in a single request.
So for instance, Impressory has an includeDirectives.scala.html
file, whose sole responsibility is to pull in all the directive templates
<-- etc -->
<script type="text/ng-template"
id="directive_ce_edit_tags.html"
>
@views.html.partials.viewcontent.directiveEditTags()
</script>
<script type="text/ng-template"
id="directive_ce_edit_settings.html"
>
@views.html.partials.viewcontent.directiveEditSettings()
</script>
<-- etc -->
In the end
In the ends, this gives me a way of writing Single Page Apps that is:
- very compact
- scalable and non-blocking
- typesafe
- looks neat and clean (a simple monadic style)
- separates concerns well (database, security, JSON conversion, etc are all handled tidily)
- above all, fast to write
This last part is particularly important — I'm employed as a research engineer, and there's only so long I can get away with cranking out code. Most of the time I want to be deciding what to build and inventing interesting solutions to interesting problems, rather than spending too much time writing the code.
No comments:
Post a Comment