AngularJS and CoffeeScript are a great combination, despite the lack of popularity of CoffeeScript in the AngularJS community. Here are a few ways CoffeeScript makes writing AngularJS code a breeze.
Short Function Definitions
Angular uses a lot of anonymous functions - as such, short function definitions are the biggest timesaver when writing Angular code in CoffeeScript. As an example, lets write an ‘enter key’ directive that evaluates an expression when the enter key is pressed, and after a delay. Not particularly useful, but this should give you a sense of how CoffeeScript and AngularJS work together.
Here’s how you’d use it:
<input enter-key="submit()" enter-key-delay="10" />
And here’s the 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);
}
});
}
});
Compared to the 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 arrow ->
is used to define a function, with the parameters in brackets in front. If a function has no parameters, the brackets are optional, as seen in the function passed to $timeout
. You can also see how brackets are optional for calling functions, so long as the function has one or more parameters.
In this short example, we’ve removed the need for the function
keyword four times in ten lines of code, and made the code a lot more readable and faster to write. There’s also no need for return
statements, as CoffeeScript (like Ruby) automatically returns the value of the last expression in a function.
Automatic Returns
Automatic (or implicit) returns are another feature that makes writing Angular code easier and faster. Here are a few examples where automatic returns come in handy. The differences are not major, but it adds up in a large codebase.
Filters
app.filter('capitalise', function (str) {
return str.charAt(0).toUpperCase() + str.slice(1);
});
app.filter 'capitalise', (str) -> str.charAt(0).toUpperCase() + str[1..]
You can also see the handy array slicing syntax with str[1..]
Factories
app.factory('urlify', function (text) {
// nb: don't use this to make things url safe in real applications
return text.toLowerCase().replace(" ", "");
});
app.factory 'urlify', (text) -> text.toLowerCase().replace " ", ""
Directives (again)
In this case, we’re using a directive definition object. CoffeeScript’s automatic returns, YAML style object syntax, and short function definitions makes this a lot more readable. This example is taken from the directive guide in the AngularJS docs.
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) { ... }
}
});
# coffeescript uses YAML style syntax for object literals.
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) -> ...
You can see we use the automatic returns and object syntax for both the directive definition object and within the compile function.
Classes
CoffeeScript’s classes are one of the nicest inclusions the language offers. 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 shorthand for this
. So @name
becomes this.name
. Additionally, prefixing an @
symbol to a function parameter automatically attaches it to this
, as seen in the Animal constructor.
Here’s the equivalent handwritten JavaScript. This isn’t identical to the CoffeeScript compiler output, 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) // alerts "snake moved 10m.", as before
As you can see, CoffeeScript creates a named function and attaches methods to its prototype. In Angular, this is useful in two places - Services, and with the new Controller as
syntax.
Services
Whilst a factory function will be injected directly into another function, a service will first be instantiated before injection. See this article for a more detailed explanation of the difference.
In the article mentioned above, the following is used as an example of a service:
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);
});
This looks just like the JavaScript that comes from a CoffeeScript class - so instead of having to directly modify the function’s prototype, we can use CoffeeScript’s classes:
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 could also define it above the service and then pass it in, but I prefer the class to be exclusively accesible via the service.
To show how dependencies are injected, lets make a gandalf that ‘comes back’ after a delay using the $timeout
service.
gandalf.service 'gandalfService',
class Gandalf
constructor: (@$timeout) -> @color = 'grey'
comeBack: (time) ->
# the 'fat arrow' in coffeescript binds the function to the existing
# value of 'this', so @color is the same variable as above.
@$timeout =>
@color = 'white'
, time
The name of the variable in the arguments will be $timeout
, so Angular’s dependency injection will still work. Attaching dependencies to the object allows us to access them in methods - such as comeBack.
CoffeeScript classes make dealing with prototypes a lot easier, and that’s without factoring in inheritance.
Controllers
In AngularJS 1.1.5, new syntax for controllers has been introduced. You can watch the egghead.io video about it here, but essentially it allows a class to be passed in as the controller instead of a function with a scope. So, the todolist example on the AngularJS homepage would have the following HTML:
<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 would 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 a great companion to AngularJS. Whilst not for everyone, the ease of writing CoffeeScript far outweighs the downsides.
There are a number of other useful CoffeeScript features that I haven’t mentioned in this article, such as default arguments, the existential operator and array and object comprehension. All together, these make CoffeeScript an enjoyable and productive language to write. If you haven’t considered it before, take a look at the official site for more details.
You are welcome to tweet me abuse or praise as you see fit.