We just launched the Weathertron, an iOS weather app. The Weathertron is written in ClojureScript with Angular.js, backed by a weather forecast and almanac server written in Clojure on the JVM. This post outlines the motivation, benefits, and tradeoffs of this tech stack.
Why not plain ClojureScript?
The early iterations of our first weather app, WeatherTable, were written in ClojureScript with the C2 data visualization library to handle all view rendering. C2 (like its namesake, D3.js) uses pure functions to map data to the DOM; as such, it encourages very idiomatic, functional code. However, we quickly ran into DOM-related performance issues on the iPhone that we could only fix via finer control of the rendering process (the very thing C2 and D3 abstract away).
We eliminated the performance problems by pre-rendering some DOM-subtrees, tracking dependencies manually via callbacks/promises, and doing wholesale innerHTML
replacements with string templates rather than in-place DOM tree walking.
However, even though the app was structured as a state machine, it was difficult to avoid all of the fiddly glitches of manual rendering.
For our next weather app, we were determined to find a robust and efficient way to declaratively sync data models and the DOM. Of the ClojureScript and JavaScript libraries we investigated, only Angular.js had a compelling story about state management. (Actually, it has extensive, design-level documentation of a coherent vision.) In addition, Angular’s templating and data-binding directives would let us transition from Hiccup-based templates to standard HTML-templating libraries (something the designers greatly appreciate).
Clojure, meet Angular
So, we’ve decided to use Angular; only one potential problem—Angular doesn’t know anything about ClojureScript. Luckily, ClojureScript (like Clojure) has excellent interop with its host platform (ClojureScript’s function, number, string, and regex types are all JavaScript’s native types). The real question, then, is not “can you use this JavaScript library” but “should you?”; do the semantics of the library complement your application design? Or will build a Dr. Jekel / Mr. Hyde abomination of ClojureScript code doomed to emit painful, prototype-twiddling, mutation-happy JavaScript?
In the case of Angular, the integration surface is actually quite nice; unlike many other full-featured JavaScript frameworks, Angular does not require that your application models inherit from magic base classes, nor does Angular lock you in the accompanying templating, routing, or server-side components. Angular models are plain JavaScript objects & arrays, and framework behaviors (like dirty checking) are ordinary function invocations. Angular’s directives—the functionality for data-binding, routing, templating, &c.—are all a la carte.
Thus, integrating ClojureScript with Angular is really just integrating ClojureScript with plain old JavaScript objects and arrays. Luckily, ClojureScript is built on protocols, a polymorphic dispatch feature introduced in Clojure 1.2. (In many ways, ClojureScript is a 2.0 implementation of Clojure; the latter is less extensible because of historical baggage in its Java implementation.) All of the core language protocols are defined at the top of ClojureScript’s core.cljs. All we need to do is teach these protocols what to do with JavaScript data structures.
For instance, we can teach ClojureScript’s ILookup
protocol about plain JavaScript objects:
(defn strkey
"Helper fn that converts keywords into strings"
[x]
(if (keyword? x)
(name x)
x))
(extend-type object
ILookup
(-lookup
([o k]
(aget o (strkey k)))
([o k not-found]
(let [s (strkey k)]
(if (goog.object.containsKey o s)
(aget o s)
not-found)))))
which allows us to use all of ClojureScript’s map querying functions and destructuring facilities against plain JavaScript objects:
(def jso
(clj->js {:a 1
:b 2
:c {:nested "key/value"}}))
(:b jso) ;;=> 2
(get-in jso [:c :nested]) ;;=> "key/value"
(let [{:keys [a b]} jso]
;;values corresponding to a and b avaliable as locals...
)
(for [[k v] jso]
;;list comprehension over our JavaScript object...
)
ClojureScript procotols can be extended in a similar fashion to JavaScript arrays. This allows us to use ClojureScript’s powerful sequence abstractions directly on JavaScript values.
For instance, consider how the clouds are drawn in the Weathertron:
Our weather data reports sky cover as a percentage value, but we quantize those values into discrete bins and combine consecutive identical cloud covers so that we can use drop shadows on the elements.
I.e., if the first five hours of the day has sky covers (50%, 62%, 95%, 98%, 100%)
, we want to render a <div>
of width two for the “partly-cloudy” hours followed by a <div>
of width three for the “cloudy” hours.
This would be an involved process in plain JavaScript, but with ClojureScript’s sequence abstractions it can be done very concisely:
(doseq [[cloud-cover-class datum-count]
(->> (:hours scope)
(map (comp name (:cloudScale scope) :sky))
(partition-by identity)
(map (juxt first count)))]
(.append $clouds
(-> (angular-el [:div.cloud])
(.addClass cloud-cover-class)
(.css "-webkit-box-flex" (str datum-count)))))
where (:cloudScale scope)
is a function that quantizes the cloud cover percentages:
#(condp < %
95 :cloudy
70 :mostly-cloudy
32 :partly-cloudy
7 :fair
0 :clear)
and (:hours scope)
is a JavaScript array of JavaScript objects corresponding to hourly weather conditions:
[{"sky": 95, "precipMM": 1.2, "date": <JS Date>, ...}, ...]
Furthermore, because we are using plain JavaScript objects in our scopes, we can leverage Angular’s built-in data-binding directives. For instance, on the weather detail panel:
Angular automatically binds the icon and description text to the current highlightedDatum
, a JavaScript object representing the hour of the weather the user is looking at:
<div class="conditions">
<div class="icon" ng-class="highlightedDatum.condition"></div>
<div class="description">
<h1 class="condition">
{{highlightedDatum.condition | conditionFormatter}}
</h1>
<div class="clock">
<span ng-show="isCurrentHour">Right now</span>
<span ng-hide="isCurrentHour" ng-switch="prefs.clock">
<i class="icon-time"></i>
<span ng-switch-when="hour-12">{{highlightedTime | date:'h:mm'}}
<span class="unit">{{highlightedTime | date:'a'}}</span>
</span>
<span ng-switch-when="hour-24">{{highlightedTime | date:'H:mm'}}</span>
</span>
</div>
</div>
<div class="temp">
{{highlightedDatum.tempC | tempFormatter:prefs.tempUnit}}
</div>
</div>
The {{ }}
are data-binding directives, and the pipes are Angular.js filters that format specific values.
E.g., the conditionFormatter
filter turns a classname like partly-cloudy
into a more attractive “Partly Cloudy”.
Filters also make application localization incredibly straightforward, since we can specify locale-specific formatters to be dependency-injected into the scope at runtime via a configuration setting rather than trying to deal with it via conditionals within the codebase.
Also notice some Angular-specific directives in element attributes; we’re using ng-switch
to let the user toggle between a 12-hour and 24-hour clock, for example.
Recap
As a general strategy, using the best parts of JavaScript from ClojureScript has been a winning strategy. There are still a few kinks to work out (especially around the seams between ClojureScript’s immutable data structures and JavaScript’s mutable ones), but I’m very happy with the results thus far.
ClojureScript is a great substrate on which to explore alternative application design patterns.
You can use it entirely as a syntactic sugar library: everything-is-an-expression with extra sweetness provided by compile-time macros.
Or you can use it just for the multi-arity function dispatch so you never have to write Array.prototype.slice.apply(arguments)
again.
Or you could leverage protocols for polymorphic dispatch against custom-defined (or JavaScript native) types.
You can take or leave the built-in immutable vectors, maps, and set data structures.
In the case of the Weathertron, we’re using immutable data structures for calculation semantics, mutable JavaScript data structures for speedy (de)serialization, and a JavaScript library, Angular.js, that’s a great fit for our domain. The point is, we’re starting to get real choices on the frontend. I’m not talking about the choice of using optional semicolons or between writing JavaScript and a language whose golden rule is “It’s just JavaScript”. Real choices.
Choose wisely.
Moar info
- Buy the Weathertron for iOS
- Official Angular.js videos
- Quick, pragmatic screencasts on Angular.js: Egghead IO
- Alternate approaches for Angular + ClojureScript which teach Angular about ClojureScript rather than teaching ClojureScript about JavaScript: Clang & Acute.
Thanks
Thanks to Darrick Wiebe, Scott Becker, Adam Wulf, and Tom White for reviewing this blog post.