Статьи

Использование Angular.js с ClojureScript

Когда я писал свой последний пост на ClojureScript , я действительно надеялся, что кто-нибудь заскочит и скажет: «Вы делаете это неправильно! Вот как.»

Я получил несколько интересных ответов, особенно на HackerNews (где этот пост был кратко на первой странице). Кажется, что здесь действительно два лагеря: новички в таком же замешательстве, как и я, и профессионалы, которые говорят, что вам просто нужно потратить время и учиться, тогда вы сможете эффективно использовать некоторые из существующих JS-сред или (лучше?) сверните свои собственные рамки ClojureScript. Говорят, это того стоит, когда ваша кодовая база станет достаточно большой.

Получение углов на работу

Во всяком случае, Грег Вебер здесь, в моем блоге, отметил, что вы можете использовать Angular с Closure — просто нужно использовать явное внедрение зависимостей. До сих пор Angular, казалось, требовал наименьшей работы с CLJS, поэтому я был рад дать ему еще один шанс. Я также нашел эту заметку по минификации в англоязычных документах очень полезной.

В конце я успешно переписал пример приложения «todo». Вот один из способов сделать это:

(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))

Последние 4 строки эквивалентны использованию этого синтаксиса массива в JavaScript:

TodoCtrl = ['$scope', CTodoCtrl];

Еще один способ сделать это — установить $injectсвойство следующим образом:

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

Как обычно, полный рабочий проект можно найти в моем репозитории GitHub .

Детали реализации

Определение функции

В приведенном выше примере я определяю функции CTodoCtrlс помощью «заводских функций». Я нахожу это немного более читабельным, но это также может быть сделано с помощью определения на месте, например:

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

К сожалению, я не смог заставить его работать с анонимными функциями (он был скомпилирован CTodoCtrl.remaining = (function CTodoCtrl.remaining() {...):

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

Это тоже не сработало (хотелось бы!)

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

Объекты, Массивы

Я не совсем доволен использованием объектов здесь — я определенно предпочел бы использовать карты Clojure следующим образом:

; 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))

К сожалению, похоже, что Angular не любит типы ClojureScript и наоборот. Выглядит как небольшое, исправимое раздражение.

ClojureScript!

Это все еще уродливо в некоторых местах и ​​не очень впечатляюще, но мне нравится использовать функциональное программирование с ClojureScript вместо циклов JavaScript.

Я имею в виду заменить это:

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

с участием:

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

И это:

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

с участием:

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

решение суда

В общем, я, наконец, вижу свет в конце туннеля. Интеграция с Angular выглядит очень многообещающе, после устранения небольших проблем взаимодействия с отображением типов она может быть довольно выразительной и простой. Я, вероятно, пока отложу Knockout и исследую Angular.