ng-annotations
v1.1.0
Published
angular wrapper based on annotations
Downloads
13
Maintainers
Readme
ng-annotation
###angular wrapper based on es7 annotations
Ng-annotations is a small javascript library that helps to produce more structured angular applications using es6 classes and es7 decorators.
This library was build with webpack in mind but should works well with the other transpilers/javascript supersets like babel or typescript (with es7 and es6 advanced features)
Index:
Installation
npm
npm install --save ng-annotations
Bower
bower install --save ng-annotations
Basic Usage
all examples in this repo and below use the babel-core library as transpiler you're free to use any other if it supports the es7 decorator feature.
webpack
a configuration example is available in the webpack dev config
import {service, inject} from 'node_modules/ng-annotations';
@service()
@inject('$http')
export default class MyService {
controller($http) {
/*do something*/
}
}
es6 files
a configuration example is available in the gruntfile
const {controller, inject} = ngAnnotations;
@controller('controllerName')
export default class theController {
controller() {
/*do something*/
}
}
Informations
All the examples below will show you the webpack way.
However, an implementation of the angular todolist with the basic es6 syntax is available in the example/es6 folder
How it works:
all component annotations add 3 properties and 1 method to the given class
$type
: String. the component type (controller, config, service...). Used by theautodeclare
method.$name
: String. the component name used by angular. Used by theautodeclare
method. Useful if you want to use the import system with the dependency injection system. With this method you'll avoid all hypothetical naming issues.
/*file1.js*/
import {service} from 'node_modules/ng-annotations';
@service()
export default class MyService {}
/*file2.js*/
import {controller, inject} from 'node_modules/ng-annotations';
// import {$name as myService} from './file1'; //before 0.1.6
import myService from './file1';
@controller()
@inject(myService)
export default class MyController {}
$component
: Object. the object/function used by angular, can be different than the original class (function wrap). Used by theautodeclare
method.autodeclare
: Function(ngModule). declares the current component to angular. (replaces the traditionalangular.module('...').controller('name', fn)
)
the ngModule parameter can be a string (angular module name) or an angular module instance.
/*autodeclare way*/
import myService from './file1';
import myController from './file2';
var app = angular.module('app', []);
// useful if you wanna use the import system with the module dependency injection system.
export app.name;
[myService, myController].forEach(component => component.autodeclare(app));
/*alternative*/
import myService from './file1';
import myController from './file2';
var app = angular.module('app', []);
export app.name; // useful if you wanna use the import system with the module dependency injection system.
app.service(myService.$name, myService.$component);
app.controller(myController.$name, myController.$component);
/*without import*/
import {service} from 'node_modules/ng-annotations';
@service()
class MyService {}
MyService.autodeclare('moduleName');
Available annotations
Utils
@inject
The inject annotation replaces the classical array syntax for declare a dependency injection
Basically, it will feed the $inject property with the list of dependencies
type: function
target: class and methods
Params:
- depsToInject: String|String[]|Component[]|Component. component(s) to inject
- ...otherDeps: (Optional) ...Strings.
Usage:
import {inject, service} from 'node_modules/ng-annotations';
import myFactory from '../factory';
@service()
@inject('$http','$q',myFactory) // could be @inject(['$http','$q',myFactory])
export default class CommunicationService {
constructor(http, $q, factory) {
this.http = http;
this.promise = $q;
this.factory = factory;
}
do() {/*do something*/}
}
Note:
The implicit dependency injection syntax is also available but shouldn't be used because of minification issues.
Usage:
import {inject, service} from 'node_modules/ng-annotations';
@service()
export default class CommunicationService {
constructor($http, $q) {
this.http = $http;
this.promise = $q;
}
do() {/*do something*/}
}
###@autobind
The autobind annotation gives the possibility to bind methods to its current context.
similar to object.method.bind(object)
type: statement
target: method only
Usage:
import {service, inject, autobind} from 'node_modules/ng-annotations';
@service()
@inject('$timeout')
export default class CommunicationService {
constructor(timeout) {
this.timeout = timeout;
this.loop();
}
@autobind
loop() {
console.log('hello');
this.timeout(this.loop, 1000);
}
}
###@attach
The attach annotation provides a shortcut to bind references across components and keep them safe.
type: function
target: attributes and methods
Params:
- source String|Component. source component
- "this" will target the current component
- path: (Optional) String. path toward the property
- split with dots.
obj.otherObj.myProperty
- split with dots.
####Usage:
// factories/user.js
import {factory, inject} from 'node_modules/ng-annotations';
@factory()
@inject('$http')
export default class User {
constructor() {
this.nested.property = 5;
}
connectedUsers = 0;
this.users = [];
load() {
this.$http.get('...').success(userlist => this.users = userlist)
}
}
// controller/user.js
import {inject,controller,attach} from 'node_modules/ng-annotations';
import UserFactory from '../factories/user.js';
@controller()
@inject(UserFactory)
class FooBarController {
@attach(UserFactory, 'users') // this.userlist will refers to UserFactory.users
userlist;
@attach(UserFactory, 'nested.property')
randomProperty;
@attach(UserFactory, 'load') // same as this.reload = factory.load.bind(factory);
reload;
clearUsers() {
this.users = []; // update the UserFactory.users property, the reference is kept.
}
}
Note:
binded target can be a function, a primitive or an object
Warning:
The binding occurs after the constructor calling, so you can't use the property at this step. use the controller parameters instead.
@conceal
the conceal decorator provides a way to declare the annotated properties as private
type: statement
target: methods and attributes
Usage:
import {factory, inject, attach, conceal} from 'node_modules/ng-annotations';
@factory()
@inject('$http')
export default class UserFactory {
@conceal
@attach('$http')
$http
@conceal
datas = [];
constructor(timeout) {
this.datas = [1,2,3];
}
method() {
return this.privateMethod();
}
@conceal
privateMethod() {}
}
Components
###@controller
declare the given class as an angular controller
type: function
Params:
- name: (Optional) String. angular controller name, by default the decorator will take the class name.
Usage:
import {controller} from 'node_modules/ng-annotations';
@controller('HelloWorld')
export default class MyController {
prop = 0;
}
Note:
With this syntax you should always use the controllerAs option and forget $scope (except in certain cases like $watch or $on usages).
Usage:
html
head
body
section(ng-controller="HelloWorld as HW") {{HW.prop}}
script(src="app.js")
###@service
declare the given class as an angular service
type: function
Params:
- name: (Optional) String. angular service name, by default the decorator will take the class name.
Usage:
import {service} from 'node_modules/ng-annotations';
@service('OtherName')
export default class MyService {
method() { return 100 * Math.random()|0 }
}
###@provider
declare the given class as an angular provider
like the native angular provider you must implement a$get
.
type: function
Params:
- name: (Optional) String. angular provider name, by default the decorator will take the class name.
Usage:
import {provider, inject} from 'node_modules/ng-annotations';
@provider()
export default class MyProvider {
@inject($http)
$get($http) {}
}
###@factory
declare the given class as an angular factory
type: function
Params:
- name: (Optional) String. angular factory name, by default the decorator will take the class name.
Usage:
import {factory} from 'node_modules/ng-annotations';
@factory()
export default class MyFactory {
items;
constructor() {
this.items = [];
}
}
by default the decorator return an instance of the factory class to angular so the example above is similar to the following code
angular.module('...')
.factory('MyFactory', function() {
this.items = [];
return angular.extend(this);
})
You can change this behaviour by defining an
$expose
method
import {factory, autobind} from 'node_modules/ng-annotations';
@factory()
export default class MyFactory {
items;
@autobind
get() {
return this.items;
}
@autobind
load(list = []) {
this.items = list;
}
$expose() {
return {
load: this.load,
get: this.get
}
}
}
angular.module('...')
.factory('MyFactory', function() {
this.items = [];
this.get = function() { return this.items; }
this.load = function(list) { this.items = list || []; }
return {
get: this.get.bind(this),
load: this.load.bind(this)
}
})
###@directive
declare the given class as an angular directive
type: function
Params:
- name: (Optional) String. angular directive name, by default the decorator will take the class name.
Usage:
import {directive} from 'node_modules/ng-annotations';
@directive('myDirective')
export default class MyDirective {
restrict = 'A';
scope = {};
link($scope, elem, attr) {
console.log('directive triggered');;
}
}
###@animation
declare the given class as an angular animation
type: function
Params:
- selector: String. css selector.
Usage:
import {animation} from 'node_modules/ng-annotations';
@animation('.foobar')
export default class FoobarAnimation {
enter(elem, done) {
elem.css('opacity', 0);
/*do something*/
}
leave(elem, done) {
elem.css('opacity', 1);
/*do something*/
}
}
###@config
declare the given class as an angular config
type: function
Usage:
import {config, inject} from 'node_modules/ng-annotations';
@config()
@inject('$routeProvider')
export default class FooBarConfiguration {
constructor(routeProvider) {
this.route = routeProvider;
this.setRoutes();
}
setRoutes() {
this.route.when('/xxx', { template: '...' })
}
}
###@run
declare the given class as an angular run
type: function
Usage:
import {run, inject} from 'node_modules/ng-annotations';
@run()
@inject('myFactory')
export default class SomeRun {
constructor(myFactory) {
this.fact = myFactory;
this.initModel();
}
initModel() {
this.fact.load();
}
}
###@filter
declare the given class as an angular filter
type: function
Params:
- properties: (Optional) Object|String. angular filter properties. contains the name and the stateful attribute
- name: String. angular filter name, by default the decorator will take the class name.
- stateful: Boolean. default false
Usage:
The decorated filter is slightly different than the original. to make it work you need to implement a
$filter
method. This is the method used by angular.
import {filter} from 'node_modules/ng-annotations';
@filter('capitalizeFilter')
export default class Capitalize {
toCapitalize(val) {
return val[0].toUpperCase() + val.slice(1);
}
$filter(val) {
return this.toCapitalize(val);
}
}
Note:
If you need to write a stateful filter, you must give a literal objet as parameter to the filter decorator
//inspired by https://docs.angularjs.org/guide/filter
import {filter, inject, attach} from 'node_modules/ng-annotations';
@filter({name:'decorate', stateful:true})
@inject('decoration')
export default Decorate {
@attach('decoration', 'symbol')
decorator;
$filter(input) {
return this.decorator + input + this.decorator;
}
}
###@decorator
provides a way to decorate an existing angular element
type: function
Params:
- name: String. angular element's name to decorate.
Usage:
import {decorator, inject} from 'node_modules/ng-annotations';
@decorator('elementName')
@inject('$delegate')
export default class DecoratedElement {
constructor($delegate) {
/*decoration*/
}
}
by default the decorator return an instance of $delegate so the example above is similar to the following code
angular.module('...')
.config(function($provide) {
$provide.decorator('elementName', [
'$delegate',
($delegate) => {
/*decoration*/
return $delegate;
}
])
})
You can change this behaviour by defining an
$decorate
method
import {decorator, inject, attach} from 'node_modules/ng-annotations';
@decorator('elementName')
@inject('$delegate')
export default class DecoratedElement {
//@attach('$delegate')
//$delegate;
sayHello() {
console.log('hello world');
}
$decorate() {
//this.$delegate.sayHello = () => this.sayHello();
//return this.$delegate;
return this;
}
}
angular.module('...')
.config(function($provide) {
$provide.decorator('elementName', [
'$delegate',
($delegate) => {
return {
sayHello() {
console.log('hello world');
}
};
}
])
})
Wrappers
the Value and Constant components can't be replaced by a class.
In order to simplify their declaration two wrappers are available.
###constant
Params:
- name: String. constant name.
- value: Mix. constant value.
Usage:
import {constant} from 'node_modules/ng-annotations';
export default constant('name', 'a constant');
###value
Params:
- name: String. value name.
- value: Mix. value value.
Usage:
import {value} from 'node_modules/ng-annotations';
export default value('name', 'a value');
Composites
The composites decorators aren't simple angular component wrappers like above, they implement new concepts on top of Angular 1.
###@component
This decorator declares the given class as a controller and creates an associated directive
With the emergence of the new components oriented frameworks like react or angular 2, the application structures changed drastically. Most of the time, when we create a directive we want also create a controller with its own logic, however create 2 files for 2 lines of code each is a waste of time. The following decorator combine a controller and a directive in the way of the angular2's @component.
type: function
Params:
- options: (Mandatory) Object. component options.
- selector: (Mandatory) String. directive's name
- alias: (Optional) String. controllerAs option, defaults to the selector value
- type: (Optional) String. directive's restrict option, defaults to
E
- ioProps: (Optional) Object. the scope properties, all props are bind with the
=
operator (two way binding) - template: (Optional) Any. directive's template option
- templateUrl: (Optional) Any. directive's templateUrl option
- transclude: (Optional) Boolean. directive's transclude option
- lifecycle: (Optional) Object array of callbacks, the available hooks are
compile
,prelink
andpostlink
the component decorator injects a $ioProps property to the working class. It contains the scope properties
Usage:
import {component, inject} from 'node_modules/ng-annotations';
@component({
selector: 'myComponent',
alias: 'MyCmp',
type: 'EA',
ioProps: {
name: 'cmpName'
},
template: `
<button ng-click="MyCmp.sayHello()">say hello</button>
`,
lifecycle: {
compile: () => { console.log('compile time'); },
prelink: () => { console.log('prelink time'); },
postlink: () => { console.log('postlink time'); }
}
})
@inject('$http')
export default class MyComponent {
sayHello() {
console.log(`Hello ${this.$ioProps.name}`);
}
}
Modify and build
npm install webpack-dev-server -g
npm install webpack
npm install
Build dist version: npm run build
Build es6 example: npm run es6
Start the dev server: npm run dev
then go to http://localhost:8080/webpack-dev-server/