ng-classify

Convert CoffeeScript classes to AngularJS modules

Module License: MIT

Added by: Sam Deering

GitHub

Repository

carylandholt/ng-classify

Stats

Stars: 199

Watchers: 199

Forks: 14

Module Description

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

Module stats last updated: 2017-10-09 16:00:04

Disclaimer: Some data on this page may have been gathered from the authors GitHub respository. If you see any mistakes or outdated information please let us know. Thanks!