Platinum Partner
css,html5,javascript,tips and tricks

Angular Tutorial Rewritten to ClojureScript

Over the last few months I learned some more ClojureScript and I finally came back to Angular. First I followed their excellent tutorial. Then I decided to rewrite it to plain Clojure and ClojureScript, and it went pretty well.

I made one change on the go – rather than load JSON files directly from disk, it talks to a Ring-provided service.

Raw files are below:
(ns tutorial.app
  (:require [tutorial.controllers :as ctrl]))

(defn ng-route [provider path route-spec]
  (.when provider path (clj->js route-spec)))

(defn ng-route-otherwise [provider route-spec]
  (.otherwise provider (clj->js route-spec)))

(doto (angular/module "phonecat" (array "phonecatFilters" "phonecatServices"))
  (.config (array "$routeProvider"
                  (fn [$routeProvider]
                    (doto $routeProvider
                      (ng-route "/phones" {:templateUrl "partials/phone-list.html"
                                           :controller ctrl/phone-list-ctrl})
                      (ng-route "/phones/:phoneId" {:templateUrl "partials/phone-detail.html"
                                                    :controller ctrl/phone-detail-ctrl})
                      (ng-route-otherwise {:redirectTo "/phones"}))))))

(ns tutorial.controllers)

(defn ^:export phone-list-ctrl [$scope Phone]
  ; Unfortunately, with custom actions (like Phone.query) the advanced
  ; compilation will lose the name, so we have to get it by string explicitly
  (aset $scope "phones" ((aget Phone "query")))
  (aset $scope "orderProp" "age"))

(aset phone-list-ctrl "$inject" (array "$scope" "Phone"))

(defn ^:export phone-detail-ctrl [$scope $routeParams Phone]
  ; Setting $scope.phone from "right hand side" and $scope.mainImageUrl
  ; from success callback is nuts, but that's what the tutorial does
  (aset $scope "phone" (.get Phone
                         (clj->js {:phoneId (aget $routeParams "phoneId")})
                         (fn [phone] (aset $scope "mainImageUrl" (first (.-images phone))))))
  (aset $scope "setImage" (partial aset $scope "mainImageUrl")))

(aset phone-detail-ctrl "$inject" (array "$scope" "$routeParams" "Phone"))

(ns tutorial.filters)

(doto (angular/module "phonecatFilters" (array))
  (.filter "checkmark"
    (fn []
      (fn [input]
        (if input "\u2713" "\u2718")))))

(ns tutorial.services)

(doto (angular/module "phonecatServices" (array "ngResource"))
  (.factory "Phone"
    (array "$resource"
           (fn [$resource]
             ($resource
               "app/phones/:phoneId"
               (clj->js {})
               (clj->js {:query {:method "GET" :params {:phoneId ""} :isArray true}}))))))

All code is available at GitHub.

It’s almost a one-to-one rewrite from JavaScript. Compared to the original, it is pretty ugly – for two reasons. The first is that I wanted it to work with advanced Closure compiler, so I had to use explicit dependencies. The second is that there is a lot JavaScript interop.

Many of those issues can be mitigated with a glue layer. It is possible to write functions or macros that would automatically generate array syntax for functions with injected dependencies, create functions automatically converting arguments with clj->js, and provide a better replacement for $scope.property = function(...){...}.

I may do that later, but firstly I wanted to have a one-to-one replacement.






Published at DZone with permission of {{ articles[0].authors[0].realName }}, DZone MVB. (source)

Opinions expressed by DZone contributors are their own.

{{ tag }}, {{tag}},

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}
{{ parent.authors[0].realName || parent.author}}

{{ parent.authors[0].tagline || parent.tagline }}

{{ parent.views }} ViewsClicks
Tweet

{{parent.nComments}}