Translation of Alexander Hill's article CoffeeScript and AngularJS . This is my first translation and I will be glad to receive any comments and corrections.AngularJS and CoffeeScript are a great combination, despite the fact that CoffeeScript is not very popular in the AngularJS community. The article will present several techniques that will “facilitate” your code on AngularJS.
Short write function definition
Angular uses a large number of anonymous functions, and as a result, using a short record of the function definition, you can save a lot of time. As an example, let's create an
'enter key' directive that evaluates the expression by pressing the
Enter key with a delay. Of course, this directive does not carry any practical significance, but it should give an idea of ​​how AngularJS and CoffeeScript interact.
So, how to use:
<input enter-key="submit()" enter-key-delay="10" />
Javascript code:
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); } }); } });
Compare it to CoffeeScript:
app.directive 'enterKey', ($timeout) -> (scope, elem, attrs) -> elem.bind 'keydown', (e) -> if e.keyCode is 13 $timeout -> scope.$apply attrs.enterKey , +attrs.enterKeyDelay
In CoffeeScript, the
-> symbol
is used to define a function with parameters enclosed in parentheses in front of it. If the function has no parameters, then no parentheses are needed; an example is the function passed in $ timeout.
We avoided using the
function keyword 4 times in 10 lines of code, while the code became more readable and the time for writing was reduced. There is also no need to use the
return operator, since CoffeeScript (like Ruby) automatically returns the value of the last expression in a function.
Automatic Returns
Automatic (or implicit) return is another advantage that makes writing Angular code faster and easier. Below are a few examples where you can see the usefulness of automatic returns. The difference, of course, is not big, but it will be noticeable in a large project.
')
Filters
app.filter('capitalise', function (str) { return str.charAt(0).toUpperCase() + str.slice(1); });
app.filter 'capitalise', (str) -> str.charAt(0) + str[1..]
The bonus in this example is the syntax for getting part of the
str.slice (1) vs. array
. str [1 ..] .
Factories
app.factory('urlify', function (text) {
app.factory 'urlify', (text) -> text.toLowerCase().replace " ", ""
Directives (again)
In this case, we use an object definition directive. Using the automatic return of CoffeeScript, YAML object-style syntax, and a short definition of the function definition makes the code much more readable. The following example is taken from the
AngularJS documentation .
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) -> ...
Auto return and object syntax are used for both the object definition directive and the
compile function.
Classes
In CoffeeScript, classes are the most enjoyable inclusions in language features. Here's what they look like:
class Animal constructor: (@name) -> move: (meters) -> alert @name + " moved #{meters}m." snake = new Animal('snake') snake.move(10) # alerts "snake moved 10m."
In CoffeeScript, the
@ symbol is a short entry for
this . Therefore,
@name
will become
this.name . In addition, adding the
@ symbol to a function parameter automatically adds it to
this , as seen in the Animal constructor.
Here is an example of similar JavaScript code. It is, of course, not identical to the
compiled CoffeeScript code, but is functionally equivalent.
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)
CoffeeScript creates a named function and adds methods to its
prototype . In Angular, this is useful in two cases: in services and with the syntax
new Controller .
Services
While the factory function will be directly added to another function, in the case of a service, an instance will be created first before it is added. A more detailed explanation of the differences can be found in
this article .
An example of a service is taken from the article mentioned above:
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); });
It looks like JavaScript code compiled from the CoffeeScript class. Therefore, instead of editing the
prototype functions directly, we can use the CoffeeScript class:
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
Here we pass the class to the service function. You can define it before the service and then transfer it to the service, but I prefer that the class be available exclusively through the service.
In order to show dependency injection, let's create a
gandalf that “returns” with a delay using the
$ timeout service.
gandalf.service 'gandalfService', class Gandalf constructor: (@$timeout) -> @color = 'grey' comeBack: (time) -> # ' ' coffeescript # 'this', @color - , . @$timeout => @color = 'white' , time
The variable name in the arguments will be
$ timeout , so Angular dependency injection will continue to work. Adding dependencies to an object allows us to have access to them in methods, for example, in
comeBack .
Controllers
AngularJS 1.1.5 introduces new controller syntax. You can watch the
video for details, but essentially the new syntax allows the class to be passed as a controller, not a function with a scope. Therefore, the html code of the todoList example on the AngularJS page will look like this:
<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>
and use this CoffeeScript class as a controller:
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
Conclusion
As you can see, CoffeeScript is an excellent companion for AngularJS. Although not for all the convenience of writing CoffeeScript outweighs its disadvantages.
There are a number of other useful functions that I did not mention in this article, such as:
default parameters ,
existential operator ,
arrays and objects . All this together makes CoffeeScript an enjoyable and productive language for writing programs. If you have not yet considered using it, go to the
official website for details.