📜 ⬆️ ⬇️

CoffeeScript and AngularJS

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) { // nb:       return text.toLowerCase().replace(" ", ""); }); 

 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) // alerts "snake moved 10m.", as before 

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.

Source: https://habr.com/ru/post/188406/


All Articles