Skip to content
Convert CoffeeScript classes to AngularJS modules
JavaScript CoffeeScript
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
dist
lib
test
.gitignore
.travis.yml
CHANGELOG.md
CONTRIBUTING.md
LICENSE
README.md
gulpfile.coffee
index.coffee
index.js
package.json

README.md

ng-classify

License Version Build Status Dependency Status

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

gulp-ng-classify

$ npm install gulp-ng-classify

Grunt

grunt-ng-classify

$ 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
  • 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 suffixed controller 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

  1. $scope
  2. this with Controller 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'}

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

You can’t perform that action at this time.