Перевод статьи Александра Хилла 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 приятным и продуктивным языком для написания программ. Если вы еще не рассматривали возможность его использования, перейдите на официальный сайт за подробностями.
Автор: fustic