ng-classy
npm install ng-classy --save-dev
An opinionated cure to angular 1's ES6 problems.
(API docs are at the bottom)
Development
npm install
npm test
npm run test-watch
Purpose
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', [
directiveOneModule.name,
directiveTwoModule.name,
serviceOneModule.name,
])
// 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';
@Component({
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', [
classy.app.name
]);
For tests, just import your app's code and use angular.mock.module(classy.app.name)
. Check test/index.spec.js.
Then for your app, just use ng-classy everywhere with the following API:
import classy from 'ng-classy';
/*
* # classy.app
* The angular module instance that your whole app shares.
* Use it for things like angular config, constants, etc: `classy.app.config(() => {})`
*/
classy.app;
/*
* # @classy.Service()
* Registers 'MyService' as an injectable service on your app.
*/
@classy.Service()
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
* }
*/
@classy.Component({
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.
*/
@classy.Component({
bind: {
someParam: '='
},
template: 'we have a parameter, {{vm.someParam}}'
}
})
@classy.State('myState', {
url: 'url/:someParam'
})
class SomeComponent {
}
Templates
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';
@Component({
bind: {
param: '='
},
template: template
})
class MyComponent {
}