Skip to content
This repository has been archived by the owner. It is now read-only.
Switch branches/tags

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time


npm install ng-classy --save-dev

An opinionated cure to angular 1's ES6 problems.

(API docs are at the bottom)


  • npm install
  • npm test
  • npm run test-watch


Use ES6 classes and ES6+ decorators. Ditch angular 1's modules.

Why? Because the following is the best angular 1 + ES6 pattern you can find, but it's full of boilerplate:

//These all `export default` angular modules
import directiveOneModule from '../directive-one';
import directiveTwoModule from '../directive-two';
import serviceOneModule from '../service-one';

//Define angular dependencies. This is pure boilerplate.
export default angular.module('myComponent', [,,,
  // Make a state that maps to our component in a decoupled manner,
  // so our component is reusable outside the state.
  // Much boilerplate.
  .config(($stateProvider) => {
    $stateProvider.state('myComponentState', {
      url: 'url/:param',
      template: '<my-component param="$stateParams.param">',
      controller: ($stateParams, $scope) => $scope.$stateParams = $stateParams
  // Declare a component.
  // Much boilerplate.
  .directive('myComponent', () => {
    return {
      restrict: 'E',
      scope: {},
      template: 'my template with a {{vm.param}} binding.'
      bindToController: {
        param: '='
      controllerAs: 'vm',
      controller: MyComponent

// Finally, we can actually implement our component.
class MyComponent {

Let's fix this situation.

  • We don't care about angular module dependencies. ES6 handles dependencies for us.
  • Let's just make everything that's imported be put onto one global angular module. Only what we explicitly load via ES6 imports will be loaded by Angular.
  • Lastly, we almost always want our states with some parameters to map to a component with attribute bindings. So let's make that built-in.
// Importing these causes them to implicitly be defined as dependencies on our angular module.
import {serviceOne} from '../service-one';
import {serviceTwo} from '../service-two';
import {directiveOne} from '../directive-one';
import {Component, State} from 'ng-classy';

  // selector: 'custom-selectors-work-too',
  bind: {
    param: '='
  template: 'my template with a {{vm.param}} binding.'
@State('myComponentState', {
  url: 'url/:param'
export class MyComponent {
  // Creates a <my-component> element directive, using the class as a
  // controller and `controllerAs: 'vm'`

  // Additionally creates a state whose template is
  // '<my-component param="$stateParams.param"></my-component>'.

Goodbye, boilerplate. Hello, ease.

API and Usage

You need to understand ES6 imports and ES6 decorators to understand this.

To use ng-classy in your app, do the following:

import classy from 'ng-classy';
import {MyComponent} from './path/to/myComponent';
// Assuming `ng-app="myApp"` exists somewhere...
angular.module('myApp', [

For tests, just import your app's code and use angular.mock.module( Check test/index.spec.js.

Then for your app, just use ng-classy everywhere with the following API:

import classy from 'ng-classy';

 * #
 * The angular module instance that your whole app shares.
 * Use it for things like angular config, constants, etc: ` => {})`

 * # @classy.Service()
 * Registers 'MyService' as an injectable service on your app.
class MyService {

 * # @classy.Component(options)
 * Registers `<my-component>` as an element directive.
 * Pass in options that map to a directive definition object.
 * Has a shortcut field, `bind`, that maps to `bindToController`.
 * `options` defaults to the following in this case:
 *  {
 *    restrict: 'E',
 *    scope: {},
 *    bindToController: options.bind || {},
 *    controllerAs: 'vm',
 *    controller: MyComponent
 *  }
  bind: {
   color: '='
  template: 'some template with a binding to color {{vm.color}}'
class MyComponent {

 * # @classy.State(name, options)
 * Must be called after `@classy.Component()` on a class.
 * Registers a new state with the the given name and state options.
 * The template will default to instantiating the given component with the url parameters as attributes.
 * See the example at the beginning of the README.
 bind: {
   someParam: '='
   template: 'we have a parameter, {{vm.someParam}}'
@classy.State('myState', {
  url: 'url/:someParam'
class SomeComponent {


If you want to separate your templates into a different file from your component, use a browserify transform or the webpack html-loader module.

import template from './template.html';

  bind: {
    param: '='
  template: template
class MyComponent {


Use Angular 1 and ES6+ with ease.




No packages published