Как стать автором
Обновить

CoffeeScript и AngularJS

JavaScript *Angular *CoffeeScript *
Перевод статьи Александра Хилла CoffeeScript and AngularJS. Это мой первый перевод и буду рад получить любые замечания и исправления.

AngularJS и CoffeeScript это отличная комбинация, не смотря на то, что CoffeeScript не пользуется большой популярностью в комьюнити AngularJS. В статье будут представлены несколько приемов, которые «облегчат» ваш код на AngularJS.

Короткая запись определения функции


В Angular используется большое число анонимных функций, и как следствие, используя короткую запись определения функции, можно сэкономить уйму времени. Как пример, давайте создадим директиву 'enter key', которая вычислияет выражение по нажатии на клавишу Enter с задержкой. Конечно, эта директива не несет в себе практической значимости, но должна дать представление о том, как взаимодействуют AngularJS и CoffeeScript.
Итак, как используем:
<input enter-key="submit()" enter-key-delay="10" />

JavaScript код:
app.directive('enterKey', function ($timeout) {
  return function (scope, elem, attrs) {
    elem.bind('keydown', function (e) {
      if (e.keyCode === 13) {
        $timeout(function () {
          scope.$apply(attrs.enterKey);
        }, +attrs.enterKeyDelay);
      }
    });
  }
});

сравните его с CoffeeScript:
app.directive 'enterKey', ($timeout) ->
  (scope, elem, attrs) ->
    elem.bind 'keydown', (e) ->
      if e.keyCode is 13
        $timeout ->
          scope.$apply attrs.enterKey
        , +attrs.enterKeyDelay

В CoffeeScript символ -> используется для определения функции с параметрами, заключенными в круглые скобки перед ней. Если же функция не имеет параметров, то скобки не нужны, примером служит функция переданная в $timeout.
Мы избежали использования ключевого слова function 4 раза в 10 строчках кода, при этом код стал более читаемым и сократилось время для его написания. Также нет необходимости использования оператора return, т.к. CoffeeScript(как и Ruby) автоматически возвращает значение последнего выражения в функции.

Automatic Returns


Автоматический (или неявный) возврат — это еще одно преимущество, которое делает написание Angular кода быстрее и проще. Ниже представлены несколько примеров где видно полезность автоматического возврата. Разница, конечно, не большая, но в большом проекте будет заметна.

Фильтры

app.filter('capitalise', function (str) {
  return str.charAt(0).toUpperCase() + str.slice(1);
});

app.filter 'capitalise', (str) -> str.charAt(0) + str[1..]

Бонусом в этом примере является синтаксис получение части массива str.slice(1) vs. str[1..].

Фабрики

app.factory('urlify', function (text) {
  // nb: не используйте это в реальных приложениях
  return text.toLowerCase().replace(" ", "");
});

app.factory 'urlify', (text) -> text.toLowerCase().replace " ", ""

Директивы (опять)

В этом случае мы используем директиву определения объекта. Использование автоматического возврата CoffeeScript, синтаксиса объектного стиля YAML и короткую запись определения функции делает код намного более читаемым. Следующий пример взят с документации AngularJS.
app.directive('directiveName', function factory(injectables) {
  return {
    priority: 0,
    template: '<div></div>',
    templateUrl: 'directive.html',
    replace: false,
    transclude: false,
    restrict: 'A',
    scope: false,
    controller: function ($scope, $element, $attrs, $transclude, otherInjectables) { ... },
    compile: function compile(tElement, tAttrs, transclude) {
      return {
        pre: function preLink(scope, iElement, iAttrs, controller) { ... },
        post: function postLink(scope, iElement, iAttrs, controller) { ... }
      }
    },
    link: function postLink(scope, iElement, iAttrs) { ... }
  }
});

app.directive 'directiveName', (injectables) ->
  priority: 0
  template: '<div></div>'
  templateUrl: 'directive.html'
  replace: false
  transclude: false
  restrict: 'A'
  scope: false
  controller: ($scope, $element, $attrs, $transclude, otherInjectables) -> ...
  compile: (tElement, tAttrs, transclude) ->
    pre: (scope, iElement, iAttrs, controller) -> ...
    post: (scope, iElement, iAttrs, controller) -> ...
  link: (scope, iElement, iAttrs) -> ...

Автоматический возврат и объектный синтаксис используются как для директивы определения объекта, так и внутри функции compile.

Классы

В CoffeeScript классы являются наиболее приятными включениями в возможности языка. Вот как они выглядят:
class Animal
  constructor: (@name) ->

  move: (meters) ->
    alert @name + " moved #{meters}m."

snake = new Animal('snake')

snake.move(10) # alerts "snake moved 10m."

В CoffeeScript символ @ является короткой записью для this. Поэтому @name станет this.name. Кроме того, добавление символа @ к параметру функции автоматически добавляет его к this, как видно в конструкторе Animal.
Вот пример аналогичного кода на JavaScript. Он, конечно, не идентичен скомпилированному коду CoffeeScript, но функционально эквивалентен.
function Animal(name) {
  this.name = name;
}

Animal.prototype.move = function (meters) {
  alert(this.name + "moved" + meters + "m.")
}

var snake = new Animal('snake')

snake.move(10) // alerts "snake moved 10m.", as before

CoffeeScript создает именованную функцию и добавляет методы к ее prototype. В Angular это полезно в двух случаях: в сервисах и с синтаксисом new Controller.

Сервисы

В то время как функция фабрика будет непосредственно добавлена в другую функцию, в случае сервиса вначале будет создан его экземпляр перед добавлением. Более подробное объяснение различий можно найти в этой статье.
Из статьи, упомянутой выше, взят пример сервиса:
var gandalf = angular.module('gandalf', []);

function Gandalf() {
  this.color = 'grey';
}
Gandalf.prototype.comeBack = function () {
  this.color = 'white';
}

gandalf.service('gandalfService', Gandalf);

var injector = angular.injector(['gandalf', 'ng']);

injector.invoke(function (gandalfService) {
  console.log(gandalfService.color);
  gandalfService.comeBack()
  console.log(gandalfService.color);
});

Выглядит как JavaScript код, скомпилированный из класса CoffeeScript. Поэтому, вместо редактирования prototype функции напрямую, мы можем использовать класс CoffeeScript:
gandalf = angular.module 'gandalf', []

gandalf.service 'gandalfService',
  class Gandalf
    constructor: -> @color = 'grey'
    comeBack: -> @color = 'white'

injector = angular.injector ['gandalf', 'ng']

injector.invoke (gandalfService) ->
  console.log gandalfService.color
  gandalfService.comeBack()
  console.log gandalfService.color

Здесь мы передаем класс в сервис функцию. Вы можете определить его до сервиса и уже затем передать в сервис, но я предпочитаю чтобы класс был доступен исключительно через сервис.
Для того чтобы показать внедрение зависимостей, давайте создадим gandalf, который «возвращается» с задержкой, используя $timeout сервис.
gandalf.service 'gandalfService',
  class Gandalf
    constructor: (@$timeout) -> @color = 'grey'
    comeBack: (time) ->
      # 'двойная стрелка' в coffeescript связывает функцию с уже существующим
      # значением 'this', поэтому @color - это та же сама переменная, что и выше.
      @$timeout =>
        @color = 'white'
      , time

Имя переменной в аргументах будет $timeout, поэтому внедрение зависимостей Angular будет продолжать работать. Добавление зависимостей к объекту позволяет нам иметь доступ к ним в методах, например, в comeBack.

Контроллеры

В AngularJS 1.1.5 представлен новый синтаксис контроллеров. Вы можете посмотреть видео для подробностей, но по сути новый синтаксис позволяет классу быть переданным как контроллер, а не функция с областью видимости. Поэтому html-код примера todoList на странице AngularJS будет выглядеть следующим образом:
<body ng-app="todoApp">
  <div ng-controller="TodoCtrl as todos">
    <span>{{todos.remaining()}} of {{todos.list.length}} remaining</span>
    [<a href="" ng-click="todos.archive()">archive</a>]
    <ul>
      <li ng-repeat="todo in todos.list">
        <input type="checkbox" ng-model="todo.done">
        <span class="done-{{todo.done}}">{{todo.text}}</span>
      </li>
    </ul>
    <form ng-submit="todos.addTodo()">
      <input type="text" ng-model="todos.input" placeholder="add a new todo">
      <input type="submit" value="add">
    </form>
  </div>
</body>

и использовать этот CoffeeScript класс в качестве контроллера:
app = angular.module 'todoApp', []

app.controller 'TodoCtrl',
  class TodoCtrl
    list: [
      text: "learn coffescript"
      done: false
    ,
      text: "learn angular"
      done: true
    ]

    addTodo: ->
      @list.push
        text: @input
        done: false
      @input = ''

    remaining: ->
      count = 0
      for todo in @list
        count += if todo.done then 0 else 1
      count

    archive: ->
      oldList = @list
      @list = []
      for todo in oldList
        unless todo.done
          @list.push todo

Заключение


Как вы могли убедиться, CoffeeScript отличный компаньон для AngularJS. Хотя не для всех удобство записи CoffeeScript перевешивает его недостатки.
Есть целый ряд и других полезных функций, о которых я не упомянул в этой статье, таких как: параметры по умолчанию, экзистенциальный оператор, массивы и объекты. Все это вместе делает CoffeeScript приятным и продуктивным языком для написания программ. Если вы еще не рассматривали возможность его использования, перейдите на официальный сайт за подробностями.
Теги:
Хабы:
Всего голосов 37: ↑24 и ↓13 +11
Просмотры 18K
Комментарии Комментарии 6