HTML5 Zone is brought to you in partnership with:

My name is Konrad Garus and I solve problems for a living. I am crazy about quality of code and life, zealous learner and believer in constant refinement and improvement. I fight stubborn ignorance and “good enough” with passion. Personally I also am a husband, father, passionate reader and music fan. Konrad is a DZone MVB and is not an employee of DZone and has posted 27 posts at DZone. You can read more from them at their website. View Full User Profile

Using Angular.js with ClojureScript

12.04.2012
| 7302 views |
  • submit to reddit

When I wrote my last post on ClojureScript, I was really hoping someone would jump in and say: “You’re doing it wrong! Here’s how.”

I did get some interesting replies, especially on HackerNews (where that post was briefly on the front page). There really seem to be two camps here: Newbies as confused as I am, and pros who say you just have to invest the time and learn, then you may be able to make good use of some of existing JS frameworks or (better?) roll your own ClojureScript frameworks. They say it’s worth it once your codebase is big enough.

Getting Angular to Work

Anyway, Greg Weber here on my blog noted that you can actually use Angular with Closure – just need to use explicit dependency injection. So far Angular seemed to require the least work with CLJS, so I was happy to give it another shot. I also found this note on minification in Angular docs very helpful.

In the end I’ve successfully rewritten the “todo” sample application. Here’s one way to do it:

(defn add-todo [scope]
  (fn []
    (.push (.-todos scope) (js-obj "text" (.-todoText scope) "done" false))
    (aset scope "todoText" "")))
 
(defn remaining [scope]
  (fn []
    (count (filter #(not (.-done %)) (.-todos scope)))))
 
(defn archive [scope]
  (fn []
    (let [arr (into-array (filter #(not (.-done %)) (.-todos scope)))]
      (aset scope "todos" arr  ))))
 
(defn CTodoCtrl [$scope]
  (def $scope.todos (array (js-obj "text" "learn angular" "done" true)))
 
  (def $scope.addTodo (add-todo $scope))
 
  (def $scope.remaining (remaining $scope))
 
  (def $scope.archive (archive $scope)))
 
(def TodoCtrl
  (array
    "$scope"
    CTodoCtrl))

The last 4 lines are equivalent of using this array syntax in JavaScript:

TodoCtrl = ['$scope', CTodoCtrl];

Another way to do it is setting the $inject property, like this:

(def TodoCtrl CTodoCtrl)
(aset TodoCtrl "$inject" (array "$scope"))

As usually, complete working project can be found at my GitHub repository.

Implementation Details

Function definition

In the above example I’m defining functions on CTodoCtrl by using “factory functions”. I find this slightly more readable, but it also can be done with in-place definitions like this:

(aset $scope "remaining"
        (fn []
          (count (filter #(not (.-done %)) (.-todos $scope)))))

Unfortunately, I was unable to get it to work with anonymous functions (it compiled to CTodoCtrl.remaining = (function CTodoCtrl.remaining() {...):

(aset $scope "remaining" #(...))

This did not work either (I wish it did!):

(defn $scope.remaining [] (...))

Objects, Arrays

I’m not quite happy with the use of objects here – I would definitely prefer to use Clojure maps like this:

; Instead of:
; (def $scope.todos (array (js-obj "text" "learn angular" "done" true)))
; Do:
(def $scope.todos [{:text "learn angular" :done true}])
; Insetad of:
; (into-array (filter #(not (.-done %)) (.-todos scope)))
; Do:
(filter #(not (:done %)) (:todos scope))

Unfortunately, it seems Angular doesn’t like ClojureScript types and vice versa. Looks like a small, fixable annoyance.

ClojureScript!

It’s still ugly at places and not quite spectacular, but I like using functional programming with ClojureScript instead of JavaScript loops.

I mean replacing this:

var count = 0;
angular.forEach($scope.todos, function(todo) {
  count += todo.done ? 0 : 1;
});
return count;

with:

(count (filter #(not (.-done %)) (.-todos scope)))

And this:

var oldTodos = $scope.todos;
$scope.todos = [];
angular.forEach(oldTodos, function(todo) {
  if (!todo.done) $scope.todos.push(todo);
});

with:

(let [arr (into-array (filter #(not (.-done %)) (.-todos scope)))]
      (aset scope "todos" arr))

Verdict

All in all, I may finally be seeing the light at the end of the tunnel. Integration with Angular looks very promising, after addressing the small interop glitches with type mapping it may be quite expressive and straightforward. I probably will shelve Knockout for now and explore Angular.








Published at DZone with permission of Konrad Garus, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)