ng-classify
Convert CoffeeScript classes to AngularJS modules
Write less JavaScript. Write less CoffeeScript. Write less Angular.Watch the screencast
Demo
Install
Install with npm
$ npm install ng-classify
Usage
CoffeeScript
ngClassify = require 'ng-classify'
content = '''
class Home extends Controller
constructor: ($log) ->
$log.info 'homeController instantiated'
'''
angularModule = ngClassify content
JavaScript
var ngClassify = require('ng-classify');
var content = '\
class Home extends Controller\n\
constructor: ($log) ->\n\
$log.info \'homeController instantiated\'\
';
var angularModule = ngClassify(content);
Gulp
$ npm install gulp-ng-classify
Grunt
$ npm install grunt-ng-classify
Ruby Gem
ng_classify - maintained by pencilcheck
$ gem install ng_classify
Brunch
ng-classify-brunch - maintained by andrejd
$ npm install ng-classify-brunch
Table of Contents
Overview
AngularJS is well suited to take advantage of the CoffeeScript class syntax. However there's still a bit of boilerplate code we have to work through. ng-classify addresses this. Note: all examples are valid CoffeeScript.
Here's how you write a controller using ng-classify
class Admin extends Controller
constructor: ($scope, someService) ->
$scope.coolMethod = someService.coolMethod()
which is equivalent to
angular.module('app').controller('adminController', ['$scope', 'someService', function ($scope, someService) {
$scope.coolMethod = someService.coolMethod();
}]);
Why?
Take the following typical AngularJS controller declaration (same as above)
angular.module('app').controller('adminController', ['$scope', 'someService', function ($scope, someService) {
$scope.coolMethod = someService.coolMethod();
}]);
So what's wrong with this?
- App name,
angular.module('app').controller
, is required within the declaration- some avoid this by the use of a global variable,
app.controller
, which is not good JavaScript hygiene
- some avoid this by the use of a global variable,
- Parameter names are duplicated, one for the getters,
'$scope', 'someService'
, and one for the function parameters,function ($scope, someService)
- this duplication is required to make the module minifiable
- some avoid this by the use of ngmin
- Depending upon the desired naming format, module type (
controller
) and module name (adminController
) have duplication, due to the suffixedcontroller
in this example - The function is anonymous (unnamed), making it more difficult to debug
- Generally verbose
How?
Write AngularJS modules using the following syntaxes.
NOTE: {{}}
denotes placeholders
class {{appName}} extends {{Animation|Config|Controller|Directive|Factory|Filter|Provider|Run|Service}}
constructor: ({{params}}) ->
# module body here
or
class {{name}} extends {{App|Constant|Value}}
constructor: ->
return {{value}}
CoffeeScript Classes
The typical way to use CoffeeScript classes with AngularJS is as follows.
# 203 characters
class AdminController
constructor: ($scope, someService) ->
$scope.coolMethod = someService.coolMethod()
angular.module('app').controller 'adminController', ['$scope', 'someService', AdminController]
which is equivalent to
// 177 characters
angular.module('app').controller('adminController', ['$scope', 'someService', function AdminController ($scope, someService) {
$scope.coolMethod = someService.coolMethod();
}]);
with ng-classify, this is all you need
# 116 characters
class Admin extends Controller
constructor: ($scope, someService) ->
$scope.coolMethod = someService.coolMethod()
Benefits
- Removes unnecessary ceremonial code (
angular.module('app')
) - App name is not required when writing a module. It is now configurable.
- Parameters are needed only once via the
constructor
function. No need for the array syntax to make your code minifiable. - No need to suffix the module name with the module type, e.g. myController, myCtrl, etc.
- The function is named, making debugging more convenient
- The syntax is arguably concise. Bring your code to the forefront with the elimination of cruft.
Considerations
- To avoid the use of global variables, it is advised to use the
bare: false
CoffeeScript compilation option. see CoffeeScript Usage
Controller As Syntax
AngularJS provides two styles for writing and consuming controllers
$scope
this
withController as
$scope
example
class Admin extends Controller
constructor: ($scope, someService) ->
$scope.coolMethod = someService.coolMethod()
view for $scope
example
<div ng-controller="adminController">
<button ng-click="coolMethod()">Cool It Down!</button>
</div>
this
example
class Admin extends Controller
constructor: (someService) ->
@coolMethod = someService.coolMethod()
view for this
example
<div ng-controller="adminController as controller">
<button ng-click="controller.coolMethod()">Cool It Down!</button>
</div>
Module Types
App
Although there is no AngularJS App module type, it is included for consistency.
class App extends App
constructor: ->
return [
'ngAnimate'
'ngRoute'
]
equivalent to
angular.module('app', [
'ngAnimate',
'ngRoute'
]);
You may wish to use the then
CoffeeScript syntax to highlight your code even more by eliminating the need for extra lines of code and indentation, as follows. Note: this can be leveraged for any CoffeeScript class.
class App extends App then constructor: -> return [
'ngAnimate'
'ngRoute'
]
Note: the app name is configured via the appName option, not the class name
Animation
class MyCrazyFader extends Animation
constructor: ->
return {
enter: (element, done) ->
# run the animation here and call done when the animation is complete
cancellation = (element) ->
# this (optional) function will be called when the animation
# completes or when the animation is cancelled (the cancelled
# flag will be set to true if cancelled).
}
equivalent to
angular.module('app').animation('.my-crazy-fader', [function MyCrazyFader () {
return {
enter: function (element, done) {
// run the animation here and call done when the animation is complete
var cancellation = function (element) {
// this (optional) function will be called when the animation
// completes or when the animation is cancelled (the cancelled
// flag will be set to true if cancelled).
};
return cancellation;
}
};
}]);
Config
class Routes extends Config
constructor: ($routeProvider) ->
$routeProvider
.when '/home',
controller: 'homeController'
templateUrl: 'home.html'
.when '/about',
controller: 'aboutController'
templateUrl: 'about.html'
.otherwise
redirectTo: '/home'
equivalent to
angular.module('app').config(['$routeProvider', function Routes ($routeProvider) {
$routeProvider
.when('/home', {
controller: 'homeController',
templateUrl: 'home.html'
})
.when('/about', {
controller: 'aboutController',
templateUrl: 'about.html'
})
.otherwise({
redirectTo: '/home'
});
}]);
Constant
class HttpStatusCodes extends Constant
constructor: ->
return {
'401': 'Unauthorized'
'403': 'Forbidden'
'404': 'Not Found'
}
equivalent to
angular.module('app').constant('HTTP_STATUS_CODES', {
'401': 'Unauthorized',
'403': 'Forbidden',
'404': 'Not Found'
});
Controller
The example below uses the this syntax
class Home extends Controller
constructor: (userService) ->
@save = (username) ->
userService.addUser username
equivalent to
angular.module('app').controller('homeController', ['userService', function Home (userService) {
this.save = function (username) {
return userService.addUser(username);
};
}]);
Directive
class Dialog extends Directive
constructor: ->
return {
restrict: 'E'
transclude: true
templateUrl: 'dialog.html'
}
equivalent to
angular.module('app').directive('dialog', [function Dialog () {
return {
restrict: 'E',
transclude: true,
templateUrl: 'dialog.html'
};
}]);
Factory
class Greeting extends Factory
constructor: ($log) ->
return {
sayHello: (name) ->
$log.info name
}
equivalent to
angular.module('app').factory('Greeting', ['$log', function Greeting ($log) {
return {
sayHello: function (name) {
$log.info(name);
}
};
}]);
Another nice feature is the ability to return classes
class User extends Factory
constructor: ($log) ->
return class UserInstance
constructor: (firstName, lastName) ->
@getFullName = ->
"#{firstName} #{lastName}"
usage
user = new User 'Cary', 'Landholt'
fullName = user.getFullName() # Cary Landholt
Filter
class Twitterfy extends Filter
constructor: ->
return (username) ->
"@#{username}"
equivalent to
angular.module('app').filter('twitterfy', [function Twitterfy () {
return function (username) {
return '@' + username;
};
}]);
Provider
class Greetings extends Provider
constructor: ($log) ->
@name = 'default'
@$get = ->
name = @name
sayHello: ->
$log.info name
@setName = (name) ->
@name = name
equivalent to
angular.module('app').provider('greetingsProvider', ['$log', function Greetings ($log) {
this.name = 'default';
this.$get = function () {
var name = this.name;
return {
sayHello: function () {
return $log.info(name);
}
};
};
this.setName = function (name) {
return this.name = name;
};
}]);
Run
class ViewsBackend extends Run
constructor: ($httpBackend) ->
$httpBackend.whenGET(/^.*\.(html|htm)$/).passThrough()
equivalent to
angular.module('app').run(['$httpBackend', function ViewsBackend ($httpBackend) {
$httpBackend.whenGET(/^.*\.(html|htm)$/).passThrough();
}]);
Service
class Greeting extends Service
constructor: ($log) ->
@sayHello = (name) ->
$log.info name
equivalent to
angular.module('app').service('greetingService', ['$log', function Greeting ($log) {
this.sayHello = function (name) {
return $log.info(name);
};
}]);
Value
class People extends Value
constructor: ->
return [
{
name: 'Luke Skywalker'
age: 26
}
{
name: 'Han Solo'
age: 35
}
]
equivalent to
angular.module('app').value('people',
[
{
name: 'Luke Skywalker',
age: 26
}, {
name: 'Han Solo',
age: 35
}
]
);
Multiple Apps
Although using multiple apps in an AngularJS application is unnecessary, some may still wish to do so.
Simply provide the app name as a parameter to the module type.
In the example below, a Controller is created within the 'common' app.
class Home extends Controller('common')
constructor: ($log) ->
$log.info 'homeController instantiated'
equivalent to
angular.module('common').controller('homeController', ['$log', function ($log) {
$log.info('homeController instantiated');
})];
API
ngClassify(content, options)
content
Required
Type: String
Default: undefined
The content that may contain CoffeeScript classes to convert to AngularJS modules
options
Type: Object
Default: undefined
options.appName
Type: String
Default: 'app'
The name of the AngularJS app
// for example
angular.module('app')
options.prefix
Type: String
Default: ''
To avoid potential collisions, the moduleType prefix may be set (ex: options.prefix = 'Ng'
)
class Home extends Ng.Controller
constructor: ($log) ->
$log.info 'homeController instantiated'
options.animation
Type: Object
Default: {format: 'spinalCase', prefix: '.'}
options.constant
Type: Object
Default: {format: 'screamingSnakeCase'}
options.controller
Type: Object
Default: {format: 'camelCase', suffix: 'Controller'}
options.directive
Type: Object
Default: {format: 'camelCase'}
options.factory
Type: Object
Default: {format: 'upperCamelCase'}
options.filter
Type: Object
Default: {format: 'camelCase'}
options.provider
Type: Object
Default: {format: 'camelCase', suffix: 'Provider'}
options.service
Type: Object
Default: {format: 'camelCase', suffix: 'Service'}
options.value
Type: Object
Default: {format: 'camelCase'}
Supported Formats
Format | Example |
---|
- | no change camelCase | camelCase lowerCamelCase | lowerCamelCase lowerCase | lowercase screamingSnakeCase | SCREAMING_SNAKE_CASE snakeCase | snake_case spinalCase | spinal-case trainCase | Train-Case upperCamelCase | UpperCamelCase upperCase | UPPERCASE
Contributing
See CONTRIBUTING.md
Changelog
See CHANGELOG.md
License
See LICENSE