├── example ├── es6 │ ├── modules │ │ └── todo │ │ │ ├── index.es6 │ │ │ ├── constants │ │ │ └── storage.es6 │ │ │ ├── directives │ │ │ ├── todolist.es6 │ │ │ ├── todoEscape.es6 │ │ │ └── todoFocus.es6 │ │ │ ├── runs │ │ │ └── todos.es6 │ │ │ ├── services │ │ │ └── storage.es6 │ │ │ ├── factories │ │ │ └── todos.es6 │ │ │ ├── controllers │ │ │ └── todolist.es6 │ │ │ └── templates │ │ │ └── todolist.es6 │ └── index.html └── webpack │ ├── app.js │ ├── modules │ ├── todo │ │ ├── constants │ │ │ └── storage.js │ │ ├── runs │ │ │ └── todos.js │ │ ├── directives │ │ │ ├── todolist.js │ │ │ ├── todoEscape.js │ │ │ └── todoFocus.js │ │ ├── services │ │ │ └── storage.js │ │ ├── factories │ │ │ └── todos.js │ │ ├── index.js │ │ ├── controllers │ │ │ └── todolist.js │ │ └── templates │ │ │ └── todolist.jade │ └── todocomponent │ │ ├── constants │ │ └── storage.js │ │ ├── runs │ │ └── todos.js │ │ ├── directives │ │ ├── todoEscape.js │ │ └── todoFocus.js │ │ ├── services │ │ └── storage.js │ │ ├── decorators │ │ └── log.js │ │ ├── factories │ │ └── todos.js │ │ ├── index.js │ │ ├── templates │ │ └── todolist.jade │ │ └── components │ │ └── todolist.js │ └── index.html ├── index.js ├── tests ├── sandbox │ ├── index.js │ ├── components │ │ ├── service.js │ │ └── factory.js │ └── utils │ │ ├── attach.js │ │ └── conceal.js ├── utils │ ├── conceal.test.js │ └── attach.test.js └── components │ ├── service.test.js │ └── factory.test.js ├── .gitignore ├── src ├── wrappers │ ├── value.js │ └── constant.js ├── decorators │ ├── utils │ │ ├── conceal.js │ │ ├── autobind.js │ │ ├── inject.js │ │ └── attach.js │ └── components │ │ ├── run.js │ │ ├── config.js │ │ ├── directive.js │ │ ├── service.js │ │ ├── provider.js │ │ ├── animation.js │ │ ├── controller.js │ │ ├── factory.js │ │ ├── decorator.js │ │ ├── filter.js │ │ └── component.js ├── app.js └── libs │ └── utils.js ├── .gitattributes ├── .npmignore ├── bower.json ├── gruntfile.js ├── karma.conf.js ├── webpack ├── webpack.dist.config.js └── webpack.dev.config.js ├── package.json ├── changelog.md ├── dist └── ng-annotations.js └── readme.md /example/es6/modules/todo/index.es6: -------------------------------------------------------------------------------- 1 | angular.module('todomvc', []); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('./dist/ng-annotations'); 2 | module.exports = ngAnnotations; -------------------------------------------------------------------------------- /example/webpack/app.js: -------------------------------------------------------------------------------- 1 | import 'npm/todomvc-common/base.css'; 2 | import 'npm/todomvc-app-css/index.css'; 3 | 4 | import 'todocomponent'; -------------------------------------------------------------------------------- /example/webpack/modules/todo/constants/storage.js: -------------------------------------------------------------------------------- 1 | import {constant} from 'src/app'; 2 | 3 | export default constant('storage-id', 'angular-todo-mvc-id'); -------------------------------------------------------------------------------- /example/webpack/modules/todocomponent/constants/storage.js: -------------------------------------------------------------------------------- 1 | import {constant} from 'src/app'; 2 | 3 | export default constant('storage-id', 'angular-todo-mvc-id'); -------------------------------------------------------------------------------- /tests/sandbox/index.js: -------------------------------------------------------------------------------- 1 | var name; 2 | 3 | try {name = angular.module('sandbox').name} 4 | catch(err) {name = angular.module('sandbox', []).name} 5 | 6 | export default name; -------------------------------------------------------------------------------- /example/es6/modules/todo/constants/storage.es6: -------------------------------------------------------------------------------- 1 | (function() { 2 | const {constant} = ngAnnotations; 3 | 4 | constant('storage-id', 'angular-todo-mvc-id') 5 | .autodeclare('todomvc'); 6 | })() 7 | 8 | -------------------------------------------------------------------------------- /example/webpack/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/es6/modules/todo/directives/todolist.es6: -------------------------------------------------------------------------------- 1 | (function() { 2 | const {directive} = ngAnnotations; 3 | 4 | @directive('todoList') 5 | class TodoList { 6 | restrict = 'EA'; 7 | scope = {}; 8 | controller = 'todoCtrl'; 9 | controllerAs = 'TodoList'; 10 | templateUrl = 'todolist.tpl'; 11 | } 12 | TodoList.autodeclare('todomvc'); 13 | 14 | })() 15 | 16 | -------------------------------------------------------------------------------- /example/webpack/modules/todo/runs/todos.js: -------------------------------------------------------------------------------- 1 | import {run, inject} from 'src/app'; 2 | 3 | import todos from '../factories/todos'; 4 | 5 | @run() 6 | @inject(todos) 7 | export default class TodoRun { 8 | 9 | constructor(todoFactory) { 10 | this._todoFactory = todoFactory; 11 | this.loadTodos(); 12 | } 13 | 14 | loadTodos() { 15 | this._todoFactory.load(); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /example/webpack/modules/todocomponent/runs/todos.js: -------------------------------------------------------------------------------- 1 | import {run, inject} from 'src/app'; 2 | 3 | import todos from '../factories/todos'; 4 | 5 | @run() 6 | @inject(todos) 7 | export default class TodoRun { 8 | 9 | constructor(todoFactory) { 10 | this._todoFactory = todoFactory; 11 | this.loadTodos(); 12 | } 13 | 14 | loadTodos() { 15 | this._todoFactory.load(); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 2 | .grunt 3 | 4 | # node-waf configuration 5 | .lock-wscript 6 | 7 | # Dependency directory 8 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 9 | node_modules 10 | 11 | example/es6/**/*.js 12 | 13 | # Bower 14 | bower_components 15 | 16 | # IntelliJ 17 | .idea 18 | *.iml 19 | -------------------------------------------------------------------------------- /example/es6/modules/todo/runs/todos.es6: -------------------------------------------------------------------------------- 1 | (function() { 2 | const {run, inject} = ngAnnotations; 3 | 4 | @run() 5 | @inject('todosFactory') 6 | class TodoRun { 7 | 8 | constructor(todoFactory) { 9 | this._todoFactory = todoFactory; 10 | this.loadTodos(); 11 | } 12 | 13 | loadTodos() { 14 | this._todoFactory.load(); 15 | } 16 | 17 | } 18 | TodoRun.autodeclare('todomvc'); 19 | 20 | })() 21 | 22 | -------------------------------------------------------------------------------- /example/webpack/modules/todo/directives/todolist.js: -------------------------------------------------------------------------------- 1 | import {directive} from 'src/app'; 2 | 3 | import {$name as todolistCtrl} from '../controllers/todolist'; 4 | import tplRenderer from '../templates/todolist.jade'; 5 | 6 | @directive('todoList') 7 | export default class TodoList { 8 | restrict = 'EA'; 9 | scope = {}; 10 | controller = todolistCtrl; 11 | controllerAs = 'TodoList'; 12 | template = tplRenderer(); 13 | } -------------------------------------------------------------------------------- /src/wrappers/value.js: -------------------------------------------------------------------------------- 1 | import utils from 'src/libs/utils'; 2 | 3 | /** 4 | * @name: @value 5 | * 6 | * declares a new angular value 7 | * 8 | * @param name value name 9 | * @param value value name 10 | * 11 | * @returns {Object} 12 | */ 13 | export default function NgValue(name, value) { 14 | var component = {}; 15 | utils.addDeclareMethod(component); 16 | utils.defineComponent(component, name, 'value', value); 17 | return component; 18 | } 19 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 2 | .grunt 3 | 4 | # node-waf configuration 5 | .lock-wscript 6 | 7 | src 8 | example 9 | 10 | # Dependency directory 11 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 12 | node_modules 13 | webpack 14 | 15 | # Bower 16 | bower_components 17 | 18 | gruntfile.js 19 | .gitignore 20 | .gitattributes 21 | 22 | # IntelliJ 23 | .idea 24 | *.iml 25 | -------------------------------------------------------------------------------- /example/webpack/modules/todo/directives/todoEscape.js: -------------------------------------------------------------------------------- 1 | import {directive} from 'src/app'; 2 | import {autobind} from 'src/app'; 3 | 4 | @directive('todoEscape') 5 | export default class TodoEscape { 6 | ESCAPE_KEY = 27; 7 | restrict = 'A'; 8 | 9 | @autobind 10 | link($scope, $node, attrs) { 11 | $node.bind('keydown', event => event.keyCode === this.ESCAPE_KEY && $scope.$apply(attrs.todoEscape)); 12 | $scope.$on('$destroy', () => $node.unbind('keydown')); 13 | } 14 | } -------------------------------------------------------------------------------- /src/wrappers/constant.js: -------------------------------------------------------------------------------- 1 | import utils from 'src/libs/utils'; 2 | 3 | /** 4 | * @name: @constant 5 | * 6 | * declares a new angular constant 7 | * 8 | * @param name constant name 9 | * @param value value name 10 | * 11 | * @returns {Object} 12 | */ 13 | export default function NgConstant(name, value) { 14 | var component = {}; 15 | utils.addDeclareMethod(component); 16 | utils.defineComponent(component, name, 'constant', value); 17 | return component; 18 | } 19 | -------------------------------------------------------------------------------- /example/webpack/modules/todocomponent/directives/todoEscape.js: -------------------------------------------------------------------------------- 1 | import {directive} from 'src/app'; 2 | import {autobind} from 'src/app'; 3 | 4 | @directive('todoEscape') 5 | export default class TodoEscape { 6 | ESCAPE_KEY = 27; 7 | restrict = 'A'; 8 | 9 | @autobind 10 | link($scope, $node, attrs) { 11 | $node.bind('keydown', event => event.keyCode === this.ESCAPE_KEY && $scope.$apply(attrs.todoEscape)); 12 | $scope.$on('$destroy', () => $node.unbind('keydown')); 13 | } 14 | } -------------------------------------------------------------------------------- /example/webpack/modules/todo/directives/todoFocus.js: -------------------------------------------------------------------------------- 1 | import {directive, inject} from 'src/app'; 2 | import {autobind} from 'src/app'; 3 | 4 | @directive('todoFocus') 5 | @inject('$timeout') 6 | export default class TodoFocus { 7 | ESCAPE_KEY = 27; 8 | restrict = 'A'; 9 | 10 | constructor($timeout) { 11 | this.timeout = $timeout; 12 | } 13 | 14 | @autobind 15 | link($scope, $node, attrs) { 16 | $scope.$watch(attrs.todoFocus, (val) => val && this.timeout(() => $node[0].focus(), 0, false)); 17 | } 18 | } -------------------------------------------------------------------------------- /example/webpack/modules/todocomponent/directives/todoFocus.js: -------------------------------------------------------------------------------- 1 | import {directive, inject} from 'src/app'; 2 | import {autobind} from 'src/app'; 3 | 4 | @directive('todoFocus') 5 | @inject('$timeout') 6 | export default class TodoFocus { 7 | ESCAPE_KEY = 27; 8 | restrict = 'A'; 9 | 10 | constructor($timeout) { 11 | this.timeout = $timeout; 12 | } 13 | 14 | @autobind 15 | link($scope, $node, attrs) { 16 | $scope.$watch(attrs.todoFocus, (val) => val && this.timeout(() => $node[0].focus(), 0, false)); 17 | } 18 | } -------------------------------------------------------------------------------- /example/es6/modules/todo/directives/todoEscape.es6: -------------------------------------------------------------------------------- 1 | (function() { 2 | const {directive, autobind} = ngAnnotations; 3 | 4 | @directive('todoEscape') 5 | class TodoEscape { 6 | ESCAPE_KEY = 27; 7 | restrict = 'A'; 8 | 9 | @autobind 10 | link($scope, $node, attrs) { 11 | $node.bind('keydown', event => event.keyCode === this.ESCAPE_KEY && $scope.$apply(attrs.todoEscape)); 12 | $scope.$on('$destroy', () => $node.unbind('keydown')); 13 | } 14 | } 15 | TodoEscape.autodeclare('todomvc'); 16 | 17 | })() 18 | 19 | -------------------------------------------------------------------------------- /example/webpack/modules/todo/services/storage.js: -------------------------------------------------------------------------------- 1 | import {service, inject} from 'src/app'; 2 | 3 | import STORAGE_CONST from '../constants/storage'; 4 | 5 | @service() 6 | @inject(STORAGE_CONST) 7 | export default class TodoStorage { 8 | storageId = ''; 9 | 10 | constructor(STORAGE_ID) { 11 | this.storageId = STORAGE_ID; 12 | } 13 | 14 | get() { 15 | return JSON.parse(localStorage.getItem(this.storageId) || '[]'); 16 | } 17 | 18 | put(todos) { 19 | localStorage.setItem(this.storageId, JSON.stringify(todos)); 20 | } 21 | } -------------------------------------------------------------------------------- /example/webpack/modules/todocomponent/services/storage.js: -------------------------------------------------------------------------------- 1 | import {service, inject} from 'src/app'; 2 | 3 | import STORAGE_CONST from '../constants/storage'; 4 | 5 | @service() 6 | @inject(STORAGE_CONST) 7 | export default class TodoStorage { 8 | storageId = ''; 9 | 10 | constructor(STORAGE_ID) { 11 | this.storageId = STORAGE_ID; 12 | } 13 | 14 | get() { 15 | return JSON.parse(localStorage.getItem(this.storageId) || '[]'); 16 | } 17 | 18 | put(todos) { 19 | localStorage.setItem(this.storageId, JSON.stringify(todos)); 20 | } 21 | } -------------------------------------------------------------------------------- /example/es6/modules/todo/services/storage.es6: -------------------------------------------------------------------------------- 1 | (function() { 2 | const {service, inject} = ngAnnotations; 3 | 4 | @service('todoStorage') 5 | @inject('storage-id') 6 | class TodoStorage { 7 | storageId = ''; 8 | 9 | constructor(STORAGE_ID) { 10 | this.storageId = STORAGE_ID; 11 | } 12 | 13 | get() { 14 | return JSON.parse(localStorage.getItem(this.storageId) || '[]'); 15 | } 16 | 17 | put(todos) { 18 | localStorage.setItem(this.storageId, JSON.stringify(todos)); 19 | } 20 | } 21 | TodoStorage.autodeclare('todomvc'); 22 | 23 | })() -------------------------------------------------------------------------------- /example/es6/modules/todo/directives/todoFocus.es6: -------------------------------------------------------------------------------- 1 | (function() { 2 | const {directive, autobind, inject} = ngAnnotations; 3 | 4 | @directive('todoFocus') 5 | @inject('$timeout') 6 | class TodoFocus { 7 | ESCAPE_KEY = 27; 8 | restrict = 'A'; 9 | 10 | constructor($timeout) { 11 | this.timeout = $timeout; 12 | } 13 | 14 | @autobind 15 | link($scope, $node, attrs) { 16 | $scope.$watch(attrs.todoFocus, (val) => val && this.timeout(() => $node[0].focus(), 0, false)); 17 | } 18 | } 19 | TodoFocus.autodeclare('todomvc'); 20 | 21 | })() 22 | 23 | -------------------------------------------------------------------------------- /example/webpack/modules/todocomponent/decorators/log.js: -------------------------------------------------------------------------------- 1 | import {decorator, inject, attach, conceal} from 'src/app'; 2 | 3 | @decorator('$log') 4 | @inject('$delegate') 5 | export default class DecoratedLogger { 6 | 7 | // @attach('$delegate') 8 | // @conceal delegate; 9 | 10 | constructor(delegate) { 11 | delegate.special = (...tolog) => this.specialLog(...tolog); 12 | } 13 | 14 | specialLog(...tolog) { 15 | console.log('SPECIAL LOGGER:', ...tolog); 16 | } 17 | 18 | // this statement is implicit 19 | // $decorate() { 20 | // return this.delegate; 21 | // } 22 | } -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-annotations", 3 | "homepage": "https://github.com/PillowPillow/ng-annotations", 4 | "authors": [ 5 | "Nicolas Gaignoux " 6 | ], 7 | "description": "angular wrapper based on es7 annotations", 8 | "main": "dist/ng-annotations.js", 9 | "keywords": [ 10 | "angular", 11 | "decorator", 12 | "annotation", 13 | "wrapper" 14 | ], 15 | "ignore": [ 16 | "src", 17 | "node_modules", 18 | "webpack", 19 | "bower_components", 20 | "gruntfile.js", 21 | ".gitignore", 22 | ".gitattributes" 23 | ], 24 | "license": "MIT" 25 | } 26 | -------------------------------------------------------------------------------- /src/decorators/utils/conceal.js: -------------------------------------------------------------------------------- 1 | import utils from 'src/libs/utils'; 2 | 3 | /** 4 | * @decorator: @conceal 5 | * @type: statement 6 | */ 7 | export default function conceal(prototype, name, descriptor) { 8 | 9 | if(name === undefined) 10 | throw Error(`@isolate decorator can only be applied to methods or attributes`); 11 | 12 | if(descriptor !== undefined) 13 | descriptor.writable = true; 14 | 15 | let $private = utils.getIdentifier('$private'); 16 | 17 | if(prototype[$private] === undefined 18 | || !(prototype[$private] instanceof Array)) 19 | prototype[$private] = []; 20 | 21 | prototype[$private].push(name); 22 | 23 | } -------------------------------------------------------------------------------- /src/decorators/utils/autobind.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @decorator: @autobind 3 | * @type: statement 4 | * 5 | * bind a method to its current context 6 | * 7 | */ 8 | export default function autobind(props, name, descriptor) { 9 | 10 | let fn = descriptor.value; 11 | 12 | if(typeof fn !== 'function') 13 | throw Error(`@autobind decorator can only be applied to methods not: ${typeof fn}`); 14 | return { 15 | configurable: true, 16 | get: function get() { 17 | var boundFn = fn.bind(this); 18 | Object.defineProperty(this, name, { 19 | value: boundFn, 20 | configurable: true, 21 | writable: true 22 | }); 23 | return boundFn; 24 | } 25 | }; 26 | } -------------------------------------------------------------------------------- /tests/sandbox/components/service.js: -------------------------------------------------------------------------------- 1 | import {service, autobind, inject} from '../../../src/app'; 2 | import app from '../index'; 3 | 4 | @inject('$http') 5 | @service() 6 | export class FooBarService { 7 | 8 | @autobind 9 | getContext() { 10 | return this; 11 | } 12 | 13 | get() { 14 | let obj = { getContext: this.getContext }; 15 | return obj.getContext(); 16 | } 17 | } 18 | 19 | @service('renamed.service.barfoo') 20 | export class BarFooService extends FooBarService {} 21 | 22 | @service() 23 | @inject(BarFooService) 24 | export class BarBarService { 25 | 26 | constructor(barfoo) { 27 | this.barfoo = barfoo; 28 | } 29 | 30 | getInjection() { 31 | return this.barfoo; 32 | } 33 | } 34 | 35 | [ 36 | FooBarService, 37 | BarFooService, 38 | BarBarService 39 | ].forEach(component => component.autodeclare(app)); 40 | -------------------------------------------------------------------------------- /tests/utils/conceal.test.js: -------------------------------------------------------------------------------- 1 | import {FooFactory,BarService,FooBarController} from '../sandbox/utils/conceal'; 2 | import module from '../sandbox'; 3 | const {mock} = angular; 4 | 5 | describe('@conceal', () => { 6 | 7 | var service, $scope, ctrlBuilder, controller, factory; 8 | beforeEach(mock.module(module)); 9 | beforeEach(mock.inject([ 10 | FooFactory.$name, 11 | BarService.$name, 12 | '$rootScope', 13 | '$controller', 14 | (fooFactory, barService, $rootScope, $controller) => { 15 | factory = fooFactory; 16 | service = barService; 17 | $scope = $rootScope.$new(); 18 | ctrlBuilder = $controller; 19 | controller = $controller(FooBarController.$name, {$scope}); 20 | } 21 | ])); 22 | 23 | it('shouldn\'t expose the concealed properties', function() { 24 | expect(service.foo).to.be.undefined; 25 | }) 26 | 27 | }) -------------------------------------------------------------------------------- /gruntfile.js: -------------------------------------------------------------------------------- 1 | var paths = {}, 2 | Path = require('path'); 3 | 4 | paths.base = Path.normalize(__dirname); 5 | 6 | module.exports = gruntConfig; 7 | 8 | function gruntConfig(grunt) { 9 | 10 | var Configuration = {}; 11 | Configuration.package = grunt.file.readJSON('package.json'); 12 | 13 | require('jit-grunt')(grunt, { 14 | 'babel': 'grunt-babel' 15 | }); 16 | require('time-grunt')(grunt); 17 | 18 | Configuration.babel = {}; 19 | 20 | Configuration.babel.dist = { 21 | options: { 22 | sourceMap: false, 23 | optional: 'es7', 24 | modules: 'common' 25 | }, 26 | files: [{ 27 | expand: true, 28 | cwd: paths.base, 29 | ext: '.js', 30 | src: ['example/es6/**/*.es6'], 31 | dest: paths.base 32 | }] 33 | }; 34 | 35 | grunt.initConfig(Configuration); 36 | 37 | grunt.registerTask('es6', ['babel:dist']); 38 | } -------------------------------------------------------------------------------- /example/webpack/modules/todo/factories/todos.js: -------------------------------------------------------------------------------- 1 | import {factory, inject} from 'src/app'; 2 | 3 | import storage from '../services/storage'; 4 | 5 | @factory('todos') 6 | @inject(storage) 7 | export default class Todos { 8 | 9 | todos = []; 10 | 11 | constructor(storage) { 12 | this._storage = storage; 13 | } 14 | 15 | load() { 16 | this.todos = this._storage.get(); 17 | } 18 | 19 | add(title) { 20 | this.todos.push({ title, completed: false }); 21 | this.update(); 22 | } 23 | 24 | update() { 25 | this._storage.put(this.todos); 26 | } 27 | 28 | remove(todo) { 29 | this.todos.splice(this.todos.indexOf(todo), 1); 30 | this.update(); 31 | } 32 | 33 | clearCompleted() { 34 | this.todos = this.todos.filter(todo => !todo.completed); 35 | this.update(); 36 | } 37 | 38 | clear() { 39 | this.todos = []; 40 | this.update(); 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /example/webpack/modules/todocomponent/factories/todos.js: -------------------------------------------------------------------------------- 1 | import {factory, inject} from 'src/app'; 2 | 3 | import storage from '../services/storage'; 4 | 5 | @factory('todos') 6 | @inject(storage) 7 | export default class Todos { 8 | 9 | todos = []; 10 | 11 | constructor(storage) { 12 | this._storage = storage; 13 | } 14 | 15 | load() { 16 | this.todos = this._storage.get(); 17 | } 18 | 19 | add(title) { 20 | this.todos.push({ title, completed: false }); 21 | this.update(); 22 | } 23 | 24 | update() { 25 | this._storage.put(this.todos); 26 | } 27 | 28 | remove(todo) { 29 | this.todos.splice(this.todos.indexOf(todo), 1); 30 | this.update(); 31 | } 32 | 33 | clearCompleted() { 34 | this.todos = this.todos.filter(todo => !todo.completed); 35 | this.update(); 36 | } 37 | 38 | clear() { 39 | this.todos = []; 40 | this.update(); 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /example/es6/modules/todo/factories/todos.es6: -------------------------------------------------------------------------------- 1 | (function() { 2 | const {factory, inject} = ngAnnotations; 3 | 4 | @factory('todosFactory') 5 | @inject('todoStorage') 6 | class Todos { 7 | 8 | todos = []; 9 | 10 | constructor(storage) { 11 | this._storage = storage; 12 | } 13 | 14 | load() { 15 | this.todos = this._storage.get(); 16 | } 17 | 18 | add(title) { 19 | this.todos.push({title, completed: false}); 20 | this.update(); 21 | } 22 | 23 | update() { 24 | this._storage.put(this.todos); 25 | } 26 | 27 | remove(todo) { 28 | this.todos.splice(this.todos.indexOf(todo), 1); 29 | this.update(); 30 | } 31 | 32 | clearCompleted() { 33 | this.todos = this.todos.filter(todo => !todo.completed); 34 | this.update(); 35 | } 36 | 37 | clear() { 38 | this.todos = []; 39 | this.update(); 40 | } 41 | 42 | } 43 | Todos.autodeclare('todomvc'); 44 | 45 | })() -------------------------------------------------------------------------------- /src/decorators/components/run.js: -------------------------------------------------------------------------------- 1 | import utils from 'src/libs/utils'; 2 | import {inject} from 'src/decorators/utils/inject'; 3 | 4 | /** 5 | * @decorator: @run 6 | * @type: function 7 | * 8 | * declares a new angular run 9 | * 10 | * @param name (optional) replaces the class name 11 | * 12 | * @returns {Function} 13 | */ 14 | export default function NgRun() { 15 | return (target) => { 16 | 17 | var component = function(...injections) { 18 | let instance = new target(...injections); 19 | utils.applyTransformations(target, instance, injections); 20 | return instance; 21 | } 22 | 23 | if(!(target.$inject instanceof Array) || target.$inject.length === 0) { 24 | var parameters = utils.extractParameters(target); 25 | if(parameters.length > 0) 26 | inject(parameters)(component); 27 | } 28 | 29 | utils.addDeclareMethod(target); 30 | utils.defineComponent(target, null, 'run', component); 31 | } 32 | } -------------------------------------------------------------------------------- /src/decorators/components/config.js: -------------------------------------------------------------------------------- 1 | import utils from 'src/libs/utils'; 2 | import {inject} from 'src/decorators/utils/inject'; 3 | 4 | /** 5 | * @decorator: @config 6 | * @type: function 7 | * 8 | * declares a new angular config 9 | * 10 | * @param name (optional) replaces the class name 11 | * 12 | * @returns {Function} 13 | */ 14 | export default function NgConfig() { 15 | return (target) => { 16 | 17 | var component = function(...injections) { 18 | let instance = new target(...injections); 19 | utils.applyTransformations(target, instance, injections); 20 | return instance; 21 | } 22 | 23 | if(!(target.$inject instanceof Array) || target.$inject.length === 0) { 24 | var parameters = utils.extractParameters(target); 25 | if(parameters.length > 0) 26 | inject(parameters)(component); 27 | } 28 | 29 | utils.addDeclareMethod(target); 30 | utils.defineComponent(target, null, 'config', component); 31 | } 32 | } -------------------------------------------------------------------------------- /src/decorators/utils/inject.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @decorator: @inject 3 | * @type: function 4 | * 5 | * replaces the angular dependency injection system 6 | * 7 | * @param toInject string|Array 8 | * @param more (optional) string[] 9 | */ 10 | export function inject(toInject, ...more) { 11 | if(!(toInject instanceof Array)) { 12 | toInject = [toInject]; 13 | if(more.length > 0) 14 | toInject = toInject.concat(more); 15 | } 16 | toInject.forEach((component, index) => { 17 | if(component instanceof Object && '$name' in component) 18 | toInject[index] = component.$name; 19 | }); 20 | 21 | return injectTo(toInject, '$inject'); 22 | } 23 | 24 | export function injectTo(toInject, targetField) { 25 | return (target, ...options) => { 26 | 27 | if(options.length > 0) 28 | target = options[1].value; 29 | 30 | Object.defineProperty(target, targetField, { 31 | value: toInject, 32 | enumerable: true, 33 | configurable: true 34 | }); 35 | } 36 | } -------------------------------------------------------------------------------- /tests/sandbox/utils/attach.js: -------------------------------------------------------------------------------- 1 | import {service, controller, attach, inject} from '../../../src/app'; 2 | import app from '../index'; 3 | 4 | @service('attach.services.foo') 5 | export class Serv { 6 | datas = []; 7 | constructor() { 8 | this.init(); 9 | } 10 | 11 | getRnd() { 12 | return 100* Math.random()|0; 13 | } 14 | 15 | init() { 16 | this.datas = [this.getRnd(),this.getRnd(),this.getRnd()]; 17 | } 18 | 19 | getLength() { 20 | return this.datas.length; 21 | } 22 | 23 | clearReference() { 24 | this.init(); 25 | } 26 | } 27 | 28 | @controller('attach.controllers.crash') 29 | export class Ctrl { 30 | @attach(Serv, 'datas') 31 | attachedData; 32 | } 33 | 34 | @controller('attach.controllers.safe') 35 | @inject(Serv) 36 | export class SafeCtrl { 37 | @attach(Serv, 'datas') 38 | attachedData; 39 | 40 | @attach(Serv, 'getLength') 41 | getLength; 42 | } 43 | 44 | 45 | [ 46 | Serv, 47 | SafeCtrl, 48 | Ctrl 49 | ].forEach(component => component.autodeclare(app)); 50 | -------------------------------------------------------------------------------- /src/decorators/components/directive.js: -------------------------------------------------------------------------------- 1 | import utils from 'src/libs/utils'; 2 | import {inject} from 'src/decorators/utils/inject'; 3 | 4 | /** 5 | * @decorator: @directive 6 | * @type: function 7 | * 8 | * declares a new angular directive 9 | * 10 | * @param name (optional) replaces the class name 11 | * 12 | * @returns {Function} 13 | */ 14 | export default function NgDirective(name = '') { 15 | return (target) => { 16 | name = name || target.name; 17 | 18 | var component = function(...injections) { 19 | let instance = new target(...injections); 20 | utils.applyTransformations(target, instance, injections); 21 | return instance; 22 | } 23 | 24 | if(!(target.$inject instanceof Array) || target.$inject.length === 0) { 25 | var parameters = utils.extractParameters(target); 26 | if(parameters.length > 0) 27 | inject(parameters)(component); 28 | } 29 | 30 | utils.addDeclareMethod(target); 31 | utils.defineComponent(target, name, 'directive', component); 32 | } 33 | } -------------------------------------------------------------------------------- /tests/components/service.test.js: -------------------------------------------------------------------------------- 1 | import {FooBarService, BarFooService, BarBarService} from '../sandbox/components/service'; 2 | import module from '../sandbox'; 3 | const {mock} = angular; 4 | 5 | describe('@service', () => { 6 | 7 | var foobar, barfoo, barbar; 8 | beforeEach(mock.module(module)); 9 | beforeEach(mock.inject([ 10 | FooBarService.$name, 11 | BarFooService.$name, 12 | BarBarService.$name, 13 | (FooBarService, BarFooService, BarBarService) => { 14 | foobar = FooBarService; 15 | barfoo = BarFooService; 16 | barbar = BarBarService; 17 | } 18 | ])); 19 | 20 | it('should have a different name', () => { 21 | expect(BarFooService.$name).to.equal('renamed.service.barfoo'); 22 | expect(barfoo).to.not.be.undefined; 23 | }) 24 | 25 | it('should keep the context', () => { 26 | expect(foobar.get()).to.equal(foobar); 27 | expect(barfoo.get()).to.equal(barfoo); 28 | }) 29 | 30 | it('should inject an other service', () => { 31 | expect(barbar.barfoo).to.equal(barfoo); 32 | }) 33 | 34 | }) -------------------------------------------------------------------------------- /src/decorators/components/service.js: -------------------------------------------------------------------------------- 1 | import utils from 'src/libs/utils'; 2 | import {inject} from 'src/decorators/utils/inject'; 3 | 4 | /** 5 | * @decorator: @service 6 | * @type: function 7 | * 8 | * declares a new angular service 9 | * 10 | * @param name (optional) replaces the class name 11 | * 12 | * @returns {Function} 13 | */ 14 | export default function NgService(name = '') { 15 | return (target) => { 16 | name = name || target.name; 17 | 18 | var component = function(...injections) { 19 | let instance = new target(...injections); 20 | utils.applyTransformations(target, instance, injections); 21 | return utils.getFinalComponent(target, instance); 22 | } 23 | 24 | if(!(target.$inject instanceof Array) || target.$inject.length === 0) { 25 | var parameters = utils.extractParameters(target); 26 | if(parameters.length > 0) 27 | inject(parameters)(target); 28 | } 29 | 30 | utils.addDeclareMethod(target); 31 | utils.defineComponent(target, name, 'service', component); 32 | } 33 | } -------------------------------------------------------------------------------- /src/decorators/components/provider.js: -------------------------------------------------------------------------------- 1 | import utils from 'src/libs/utils'; 2 | import {inject} from 'src/decorators/utils/inject'; 3 | 4 | /** 5 | * @decorator: @provider 6 | * @type: function 7 | * 8 | * declares a new angular provider 9 | * 10 | * @param name (optional) replaces the class name 11 | * 12 | * @returns {Function} 13 | */ 14 | export default function NgProvider(name = '') { 15 | return (target) => { 16 | name = name || target.name; 17 | 18 | var component = function(...injections) { 19 | let instance = new target(...injections); 20 | utils.applyTransformations(target, instance, injections); 21 | return utils.getFinalComponent(target, instance); 22 | } 23 | 24 | if(!(target.$inject instanceof Array) || target.$inject.length === 0) { 25 | var parameters = utils.extractParameters(target); 26 | if(parameters.length > 0) 27 | inject(parameters)(component); 28 | } 29 | 30 | utils.addDeclareMethod(target); 31 | utils.defineComponent(target, name, 'provider', component); 32 | } 33 | } -------------------------------------------------------------------------------- /src/decorators/components/animation.js: -------------------------------------------------------------------------------- 1 | import utils from 'src/libs/utils'; 2 | import {inject} from 'src/decorators/utils/inject'; 3 | 4 | /** 5 | * @decorator: @animation 6 | * @type: function 7 | * 8 | * declares a new angular animation 9 | * 10 | * @param name (optional) replaces the class name 11 | * 12 | * @returns {Function} 13 | */ 14 | export default function NgAnimation(name = '') { 15 | return (target) => { 16 | name = name || target.name; 17 | 18 | var component = function(...injections) { 19 | let instance = new target(...injections); 20 | utils.applyTransformations(target, instance, injections); 21 | return utils.getFinalComponent(target, instance); 22 | } 23 | 24 | if(!(target.$inject instanceof Array) || target.$inject.length === 0) { 25 | var parameters = utils.extractParameters(target); 26 | if(parameters.length > 0) 27 | inject(parameters)(component); 28 | } 29 | 30 | utils.addDeclareMethod(target); 31 | utils.defineComponent(target, name, 'animation', component); 32 | } 33 | } -------------------------------------------------------------------------------- /src/decorators/components/controller.js: -------------------------------------------------------------------------------- 1 | import utils from 'src/libs/utils'; 2 | import {inject} from 'src/decorators/utils/inject'; 3 | 4 | /** 5 | * @decorator: @controller 6 | * @type: function 7 | * 8 | * declares a new angular controller 9 | * 10 | * @param name (optional) replaces the class name 11 | * 12 | * @returns {Function} 13 | */ 14 | export default function NgController(name = '') { 15 | return (target) => { 16 | name = name || target.name; 17 | 18 | var component = function(...injections) { 19 | let instance = new target(...injections); 20 | utils.applyTransformations(target, instance, injections); 21 | return utils.getFinalComponent(target, instance); 22 | } 23 | 24 | if(!(target.$inject instanceof Array) || target.$inject.length === 0) { 25 | var parameters = utils.extractParameters(target); 26 | if(parameters.length > 0) 27 | inject(parameters)(component); 28 | } 29 | 30 | utils.addDeclareMethod(target); 31 | utils.defineComponent(target, name, 'controller', component); 32 | } 33 | } -------------------------------------------------------------------------------- /tests/sandbox/components/factory.js: -------------------------------------------------------------------------------- 1 | import {factory, autobind, inject} from '../../../src/app'; 2 | import app from '../index'; 3 | 4 | @inject('$http') 5 | @factory('factory.factories.foobar') 6 | export class FooBarFactory { 7 | 8 | @autobind 9 | getContext() { 10 | return this; 11 | } 12 | 13 | get() { 14 | let obj = { getContext: this.getContext }; 15 | return obj.getContext(); 16 | } 17 | } 18 | 19 | @factory('factory.factories.barfoo') 20 | export class BarFooFactory { 21 | 22 | foo = 0; 23 | bar = 0; 24 | 25 | $expose() { 26 | let self = this; 27 | return { 28 | get foo() { return self.foo; } 29 | } 30 | } 31 | } 32 | 33 | @factory('factory.factories.barbar') 34 | @inject(BarFooFactory) 35 | export class BarBarFactory { 36 | 37 | constructor(barfoo) { 38 | this.barfoo = barfoo; 39 | } 40 | 41 | getContext() { 42 | return this; 43 | } 44 | 45 | getInjection() { 46 | return this.barfoo; 47 | } 48 | } 49 | 50 | 51 | [ 52 | FooBarFactory, 53 | BarFooFactory, 54 | BarBarFactory 55 | ].forEach(component => component.autodeclare(app)); 56 | -------------------------------------------------------------------------------- /example/es6/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/decorators/components/factory.js: -------------------------------------------------------------------------------- 1 | import utils from 'src/libs/utils'; 2 | import {inject} from 'src/decorators/utils/inject'; 3 | 4 | /** 5 | * @decorator: @factory 6 | * @type: function 7 | * 8 | * declares a new angular factory 9 | * 10 | * @param name (optional) replaces the class name 11 | * 12 | * @returns {Function} 13 | */ 14 | export default function NgFactory(name = '') { 15 | return (target) => { 16 | name = name || target.name; 17 | 18 | var component = function(...injections) { 19 | let instance = new target(...injections); 20 | utils.applyTransformations(target, instance, injections); 21 | 22 | let exposed = utils.getFinalComponent(target, instance); 23 | return exposed.$expose instanceof Function ? exposed.$expose() : exposed; 24 | } 25 | 26 | if(!(target.$inject instanceof Array) || target.$inject.length === 0) { 27 | var parameters = utils.extractParameters(target); 28 | if(parameters.length > 0) 29 | inject(parameters)(component); 30 | } 31 | utils.addDeclareMethod(target); 32 | utils.defineComponent(target, name, 'factory', component); 33 | } 34 | } -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | var webpackconfig = require(__dirname + '/webpack/webpack.dev.config.js'); 2 | 3 | module.exports = function(config) { 4 | 5 | var configuration = {}; 6 | 7 | 8 | configuration.browsers =['Firefox']; 9 | 10 | configuration.frameworks = ['mocha','chai']; 11 | configuration.files = ['*.js']; 12 | 13 | 14 | configuration.files = [ 15 | 'node_modules/angular/angular.js', 16 | 'node_modules/angular-mocks/angular-mocks.js', 17 | 'tests/*.test.js', 18 | 'tests/**/*.test.js' 19 | ]; 20 | 21 | configuration.reporters = ['mocha']; 22 | 23 | configuration.preprocessors = { 24 | 'tests/*.js': ['webpack'], 25 | 'tests/**/*.js': ['webpack'] 26 | }; 27 | 28 | configuration.webpack = { 29 | resolve: webpackconfig.resolve, 30 | module: webpackconfig.module 31 | }; 32 | 33 | configuration.webpackMiddleware = { 34 | noInfo: true 35 | }; 36 | 37 | configuration.phantomjsLauncher = { 38 | exitOnResourceError: true 39 | }; 40 | 41 | configuration.plugins = [ 42 | require('karma-webpack'), 43 | require('karma-mocha'), 44 | require('karma-chai'), 45 | require('karma-mocha-reporter'), 46 | require('karma-firefox-launcher') 47 | ]; 48 | 49 | config.set(configuration); 50 | } -------------------------------------------------------------------------------- /tests/sandbox/utils/conceal.js: -------------------------------------------------------------------------------- 1 | import {controller,service,factory,attach,inject,conceal} from '../../../src/app'; 2 | import app from '../index'; 3 | 4 | @factory('conceal.factories.foo') 5 | export class FooFactory { 6 | 7 | datas; 8 | constructor() { 9 | this.reload(); 10 | } 11 | 12 | @conceal 13 | getRandomNumber() { 14 | return 10*Math.random()|0; 15 | } 16 | 17 | @conceal 18 | clear() { 19 | this.datas = []; 20 | } 21 | 22 | @conceal 23 | load() { 24 | let length = this.getRandomNumber(); 25 | for(let i = 0; i component.autodeclare(app)); 69 | -------------------------------------------------------------------------------- /example/webpack/modules/todo/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'npm/angular'; 2 | 3 | import storageConst from './constants/storage'; 4 | import todoRun from './runs/todos'; 5 | import storageService from './services/storage'; 6 | import todoFactory from './factories/todos'; 7 | import todoCtrl from './controllers/todolist'; 8 | import todolistDirective from './directives/todolist'; 9 | import todoEscapeDirective from './directives/todoEscape'; 10 | import todoFocusDirective from './directives/todoFocus'; 11 | 12 | const app = angular.module('todomvc', []); 13 | export default app.name; 14 | 15 | 16 | //app.constant(storageConst.$name, storageConst.$component); 17 | //app.run(todoRun.$component); 18 | //app.service(storageService.$name, storageService.$component); 19 | //app.factory(todoFactory.$name, todoFactory.$component); 20 | //app.controller(todoCtrl.$name, todoCtrl.$component); 21 | //app.directive(todolistDirective.$name, todolistDirective.$component); 22 | //app.directive(todoEscapeDirective.$name, todoEscapeDirective.$component); 23 | //app.directive(todoFocusDirective.$name, todoFocusDirective.$component); 24 | 25 | [ 26 | storageConst, 27 | todoRun, 28 | storageService, 29 | todoFactory, 30 | todoCtrl, 31 | todolistDirective, 32 | todoEscapeDirective, 33 | todoFocusDirective 34 | ].forEach(component => component.autodeclare(app)); -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | var NgAnnotations = {}; 2 | 3 | // components 4 | NgAnnotations.controller = require('src/decorators/components/controller'); 5 | NgAnnotations.component = require('src/decorators/components/component'); 6 | NgAnnotations.service = require('src/decorators/components/service'); 7 | NgAnnotations.animation = require('src/decorators/components/animation'); 8 | NgAnnotations.config = require('src/decorators/components/config'); 9 | NgAnnotations.directive = require('src/decorators/components/directive'); 10 | NgAnnotations.factory = require('src/decorators/components/factory'); 11 | NgAnnotations.filter = require('src/decorators/components/filter'); 12 | NgAnnotations.provider = require('src/decorators/components/provider'); 13 | NgAnnotations.run = require('src/decorators/components/run'); 14 | NgAnnotations.decorator = require('src/decorators/components/decorator'); 15 | 16 | // wrappers 17 | NgAnnotations.constant = require('src/wrappers/constant'); 18 | NgAnnotations.value = require('src/wrappers/value'); 19 | 20 | // utils 21 | NgAnnotations.inject = require('src/decorators/utils/inject').inject; 22 | NgAnnotations.autobind = require('src/decorators/utils/autobind'); 23 | NgAnnotations.attach = require('src/decorators/utils/attach'); 24 | NgAnnotations.conceal = require('src/decorators/utils/conceal'); 25 | 26 | export default window.ngAnnotations = NgAnnotations; -------------------------------------------------------------------------------- /example/webpack/modules/todocomponent/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'npm/angular'; 2 | 3 | import storageConst from './constants/storage'; 4 | import todoRun from './runs/todos'; 5 | import storageService from './services/storage'; 6 | import todoFactory from './factories/todos'; 7 | import todoCmp from './components/todolist'; 8 | import decoratedLogger from './decorators/log'; 9 | //import todoCtrl from './controllers/todolist'; 10 | //import todolistDirective from './directives/todolist'; 11 | import todoEscapeDirective from './directives/todoEscape'; 12 | import todoFocusDirective from './directives/todoFocus'; 13 | 14 | const app = angular.module('todomvc', []); 15 | export default app.name; 16 | 17 | 18 | //app.constant(storageConst.$name, storageConst.$component); 19 | //app.run(todoRun.$component); 20 | //app.service(storageService.$name, storageService.$component); 21 | //app.factory(todoFactory.$name, todoFactory.$component); 22 | //app.controller(todoCtrl.$name, todoCtrl.$component); 23 | //app.directive(todolistDirective.$name, todolistDirective.$component); 24 | //app.directive(todoEscapeDirective.$name, todoEscapeDirective.$component); 25 | //app.directive(todoFocusDirective.$name, todoFocusDirective.$component); 26 | 27 | 28 | [ 29 | storageConst, 30 | todoRun, 31 | storageService, 32 | todoFactory, 33 | todoCmp, 34 | //todoCtrl, 35 | //todolistDirective, 36 | todoEscapeDirective, 37 | todoFocusDirective, 38 | decoratedLogger 39 | ].forEach(component => component.autodeclare(app)); 40 | -------------------------------------------------------------------------------- /tests/utils/attach.test.js: -------------------------------------------------------------------------------- 1 | import {Ctrl, SafeCtrl, Serv} from '../sandbox/utils/attach'; 2 | import module from '../sandbox'; 3 | const {mock} = angular; 4 | 5 | describe('@attach', () => { 6 | 7 | var service, $scope, ctrlBuilder, controller; 8 | beforeEach(mock.module(module)); 9 | beforeEach(mock.inject([ 10 | Serv.$name, 11 | '$rootScope', 12 | '$controller', 13 | (serv, $rootScope, $controller) => { 14 | service = serv; 15 | $scope = $rootScope.$new(); 16 | ctrlBuilder = $controller; 17 | controller = $controller(SafeCtrl.$name, {$scope}); 18 | } 19 | ])); 20 | 21 | it('should throw an error because the @inject is not applied', function() { 22 | expect(function() { 23 | controller = ctrlBuilder(Ctrl.$name, {$scope}); 24 | }).to.throw(Error); 25 | }) 26 | 27 | it('should bind the service datas to the controller ', function() { 28 | expect(controller.attachedData).to.not.be.undefined; 29 | expect(controller.attachedData).to.equal(service.datas); 30 | }) 31 | 32 | it('should keep the context', function() { 33 | controller.attachedData.pop(); 34 | expect(controller.getLength()).to.equal(service.getLength()); 35 | service.datas.pop(); 36 | expect(controller.getLength()).to.equal(service.getLength()); 37 | }) 38 | 39 | it(`shouldn't be affected by a reference erasing`, function() { 40 | let oldDatas = service.datas; 41 | service.clearReference(); 42 | expect(oldDatas).to.not.equal(service.datas); 43 | expect(controller.attachedData).to.equal(service.datas); 44 | }) 45 | 46 | }) -------------------------------------------------------------------------------- /src/decorators/components/decorator.js: -------------------------------------------------------------------------------- 1 | import utils from 'src/libs/utils'; 2 | import {inject, injectTo} from 'src/decorators/utils/inject'; 3 | 4 | /** 5 | * @decorator: @decorator 6 | * @type: function 7 | * 8 | * declares a new angular decorator 9 | * 10 | * @param name (optional) replaces the class name 11 | * 12 | * @returns {Function} 13 | */ 14 | export default function NgDecorator(name = '') { 15 | return (target) => { 16 | name = name || target.name; 17 | 18 | var $delegatefn = function(...injections) { 19 | let $inject = target.$inject || ['$delegate']; 20 | let delegateIndex = $inject.indexOf('$delegate'); 21 | 22 | let instance = new target(...injections); 23 | utils.applyTransformations(target, instance, injections); 24 | 25 | let exposed = utils.getFinalComponent(target, instance); 26 | return exposed.$decorate instanceof Function ? exposed.$decorate() : injections[delegateIndex]; 27 | }; 28 | 29 | var component = function($provide) { 30 | var injections = target.$inject || []; 31 | if(!~injections.indexOf('$delegate')) injections.push('$delegate'); 32 | $provide.decorator(name, [...injections, $delegatefn]); 33 | }; 34 | 35 | 36 | injectTo(['$provide'], '_$inject')(component); 37 | if(!(target.$inject instanceof Array) || target.$inject.length === 0) { 38 | var parameters = utils.extractParameters(target); 39 | if(parameters.length > 0) 40 | inject(parameters)(target); 41 | } 42 | 43 | utils.addDeclareMethod(target); 44 | utils.defineComponent(target, null, 'config', component); 45 | } 46 | } -------------------------------------------------------------------------------- /tests/components/factory.test.js: -------------------------------------------------------------------------------- 1 | import {FooBarFactory, BarFooFactory, BarBarFactory} from '../sandbox/components/factory'; 2 | import module from '../sandbox'; 3 | const {mock} = angular; 4 | 5 | describe('@factory', () => { 6 | 7 | var foobar, barfoo, barbar; 8 | beforeEach(mock.module(module)); 9 | beforeEach(mock.inject([ 10 | FooBarFactory.$name, 11 | BarFooFactory.$name, 12 | BarBarFactory.$name, 13 | (FooBarFactory, BarFooFactory, BarBarFactory) => { 14 | foobar = FooBarFactory; 15 | barfoo = BarFooFactory; 16 | barbar = BarBarFactory; 17 | } 18 | ])); 19 | 20 | it('should have a different name', () => { 21 | expect(BarBarFactory.$name).to.equal('factory.factories.barbar'); 22 | expect(barbar).to.not.be.undefined; 23 | }) 24 | 25 | it('should keep the context', () => { 26 | expect(foobar.get()).to.equal(foobar); 27 | }) 28 | 29 | it('should inject an other factory', () => { 30 | expect(barbar.barfoo).to.equal(barfoo); 31 | }) 32 | 33 | it('should only return the exposed properties', () => { 34 | expect(barfoo.foo).to.not.be.undefined; 35 | expect(barfoo.bar).to.be.undefined; 36 | }) 37 | 38 | it('should provide apply and call methods', function() { 39 | expect(barbar.getContext.apply).to.not.be.undefined; 40 | expect(barbar.getContext.call).to.not.be.undefined; 41 | expect(barbar.getContext.apply).to.be.an.instanceOf(Function); 42 | expect(barbar.getContext.call).to.be.an.instanceOf(Function); 43 | }) 44 | 45 | it('should change the execution context', function() { 46 | var scope = {foo:3}; 47 | expect(barbar.getContext.call(scope)).to.equal(scope); 48 | expect(barbar.getContext.apply(scope)).to.equal(scope); 49 | }) 50 | 51 | }) -------------------------------------------------------------------------------- /webpack/webpack.dist.config.js: -------------------------------------------------------------------------------- 1 | var config = {}; 2 | 3 | /*modules*/ 4 | var Path = require('path'), 5 | webpack = require('webpack'), 6 | pckg = require('../package.json') 7 | 8 | /*consts*/ 9 | var project = Path.join(__dirname, '..'), 10 | wdir = Path.join(project, 'src'), 11 | entryfile = Path.join(wdir, 'app.js'), 12 | dist = Path.join(project, 'dist'), 13 | styles = Path.join(wdir, 'styles'), 14 | node_modules = Path.join(project, 'node_modules'), 15 | bower_components = Path.join(project, 'bower_components'); 16 | 17 | config.entryfile = entryfile; 18 | config.devtool = 'source-map'; 19 | config.entry = entryfile; 20 | 21 | config.cache = true; 22 | 23 | config.output = { 24 | publicPath: '/', 25 | filename: pckg.name + '.js', 26 | path: dist, 27 | chunkFilename: '[chunkhash].bundle.js' 28 | }; 29 | 30 | config.resolve = {}; 31 | config.resolve.extensions = ['', '.js', '.jsx']; 32 | config.resolve.root = wdir; 33 | 34 | config.module = {}; 35 | config.module.loaders = [ 36 | { 37 | test: /\.js$/, 38 | loader: 'babel?experimental&optional=es7', 39 | exclude: [node_modules, bower_components] 40 | }, 41 | { 42 | test: /\.jsx$/, 43 | loader: 'babel?experimental&optional=es7', 44 | exclude: [node_modules, bower_components] 45 | } 46 | ]; 47 | 48 | 49 | config.resolve.alias = { 50 | src: wdir, 51 | core: entryfile 52 | }; 53 | 54 | config.plugins = [ 55 | new webpack.optimize.OccurenceOrderPlugin(), 56 | new webpack.optimize.DedupePlugin(), 57 | new webpack.NoErrorsPlugin(), 58 | new webpack.optimize.UglifyJsPlugin({ 59 | minimize: true, 60 | sourceMap: true, 61 | compress: { warnings: false }, 62 | mangle: { 63 | except: ['exports', 'require', 'module'] 64 | } 65 | }) 66 | ]; 67 | 68 | module.exports = config; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-annotations", 3 | "version": "1.1.0", 4 | "description": "angular wrapper based on annotations", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "webpack --progress -c --config webpack/webpack.dist.config.js", 8 | "dev": "webpack-dev-server --port 8080 --content-base example/webpack --hot --progress -c --config webpack/webpack.dev.config.js", 9 | "es6": "grunt es6", 10 | "unit": "karma start" 11 | }, 12 | "keywords": [ 13 | "angular", 14 | "decorator", 15 | "annotation", 16 | "wrapper" 17 | ], 18 | "repository": { 19 | "type": "git", 20 | "url": "git@github.com:PillowPillow/ng-annotations.git" 21 | }, 22 | "authors": [ 23 | "Nicolas Gaignoux " 24 | ], 25 | "license": "MIT", 26 | "devDependencies": { 27 | "angular": "^1.4.8", 28 | "angular-mocks": "^1.4.8", 29 | "babel-core": "^5.8.21", 30 | "babel-loader": "^5.3.2", 31 | "babel-runtime": "^5.8.20", 32 | "chai": "^3.2.0", 33 | "css-loader": "^0.15.5", 34 | "extract-text-webpack-plugin": "^0.8.2", 35 | "file-loader": "^0.8.4", 36 | "grunt": "^0.4.5", 37 | "grunt-babel": "^5.0.1", 38 | "jade": "^1.11.0", 39 | "jade-loader": "^0.7.1", 40 | "jit-grunt": "^0.9.1", 41 | "karma": "^0.13.8", 42 | "karma-chai": "^0.1.0", 43 | "karma-firefox-launcher": "^0.1.6", 44 | "karma-mocha": "^0.2.0", 45 | "karma-mocha-reporter": "^1.1.1", 46 | "karma-webpack": "^1.7.0", 47 | "mocha": "^2.2.5", 48 | "node-sass": "^3.2.0", 49 | "style-loader": "^0.12.3", 50 | "time-grunt": "^1.2.1", 51 | "todomvc-app-css": "^2.0.1", 52 | "todomvc-common": "^1.0.2", 53 | "url-loader": "^0.5.6", 54 | "webpack": "^1.10.5" 55 | }, 56 | "dependencies": { 57 | "angular": "^1.5.7" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/decorators/components/filter.js: -------------------------------------------------------------------------------- 1 | import utils from 'src/libs/utils'; 2 | import {inject} from 'src/decorators/utils/inject'; 3 | 4 | /** 5 | * @decorator: @filter 6 | * @type: function 7 | * 8 | * declares a new angular filter 9 | * 10 | * @param filterProps (optional) filter properties containing name and the stateful attribute 11 | * 12 | * @returns {Function} 13 | */ 14 | export default function NgFilter(filterProps = {name:'',stateful:false}) { 15 | 16 | return (target) => { 17 | 18 | let name = '', stateful = false; 19 | if(filterProps instanceof Object){ 20 | name = filterProps.name || target.name; 21 | stateful = !!filterProps.stateful; 22 | } 23 | else 24 | name = filterProps || target.name; 25 | 26 | var component = function(...injections) { 27 | let instance = new target(...injections); 28 | 29 | if(!(instance.$filter instanceof Function)) 30 | throw Error('an annotated "filter" must implement the "$filter" method'); 31 | utils.applyTransformations(target, instance, injections); 32 | 33 | //@todo remove it in the next version 34 | if(instance.$stateful === true) { 35 | console.warn('the $stateful property is deprecated and will be removed in the next versions, use the @filter parameter instead'); 36 | console.warn('https://github.com/PillowPillow/ng-annotations#d_filter'); 37 | filter.$stateful = true; 38 | } 39 | 40 | if(stateful) 41 | filter.$stateful = stateful; 42 | 43 | return filter; 44 | function filter(...parameters) { 45 | return instance.$filter(...parameters); 46 | } 47 | } 48 | 49 | if(!(target.$inject instanceof Array) || target.$inject.length === 0) { 50 | var parameters = utils.extractParameters(target); 51 | if(parameters.length > 0) 52 | inject(parameters)(component); 53 | } 54 | 55 | utils.addDeclareMethod(target); 56 | utils.defineComponent(target, name, 'filter', component); 57 | } 58 | } -------------------------------------------------------------------------------- /example/webpack/modules/todo/controllers/todolist.js: -------------------------------------------------------------------------------- 1 | import {controller, inject, attach, conceal} from 'src/app'; 2 | 3 | import todos from '../factories/todos'; 4 | 5 | @controller() 6 | @inject('$filter', todos) 7 | export default class TodoList { 8 | 9 | statusFilter = {}; 10 | newTodo = ''; 11 | editedTodo = null; 12 | originalTodo = null; 13 | 14 | @attach(todos) 15 | @conceal 16 | todoFactory; 17 | 18 | @conceal 19 | filter; 20 | 21 | @attach(todos, 'todos') 22 | todos; 23 | 24 | constructor($filter) { 25 | this.filter = $filter('filter'); 26 | } 27 | 28 | get allChecked() { 29 | return this.nbRemaining === 0; 30 | } 31 | 32 | get nbRemaining() { 33 | return this.filter(this.todos, {completed: false}).length; 34 | } 35 | 36 | setFilter(status) { 37 | switch(status) { 38 | case 'active': 39 | this.statusFilter = { completed: false }; 40 | break; 41 | case 'completed': 42 | this.statusFilter = { completed: true }; 43 | break; 44 | default: 45 | this.statusFilter = {}; 46 | break; 47 | } 48 | } 49 | 50 | add() { 51 | var title = this.newTodo.trim(); 52 | if(!title) return; 53 | 54 | this.todoFactory.add(title); 55 | this.newTodo = ''; 56 | } 57 | 58 | edit(todo) { 59 | this.editedTodo = todo; 60 | this.originalTodo = angular.extend({}, todo); 61 | } 62 | 63 | @attach(todos, 'remove') 64 | remove; 65 | 66 | doneEditing(todo) { 67 | this.editedTodo = null; 68 | todo.title = todo.title.trim(); 69 | !todo.title && this.todoFactory.remove(todo); 70 | } 71 | 72 | @attach(todos, 'update') 73 | statusEdited; 74 | 75 | revert(todo) { 76 | let index = this.todos.indexOf(todo); 77 | if(!~index) return; 78 | 79 | this.todos[index] = this.originalTodo; 80 | this.doneEditing(this.originalTodo); 81 | } 82 | 83 | markAll() { 84 | let completed = this.allChecked; 85 | this.todos.forEach(todo => todo.completed = !completed); 86 | }; 87 | 88 | } -------------------------------------------------------------------------------- /example/webpack/modules/todo/templates/todolist.jade: -------------------------------------------------------------------------------- 1 | section.todoapp 2 | 3 | header.header 4 | h1 Todos 5 | form.todo-form(ng-submit="TodoList.add()") 6 | input.new-todo(placeholder="What needs to be done?", ng-model="TodoList.newTodo", autofocus) 7 | 8 | section.main(ng-show="TodoList.todos.length > 0", ng-cloak) 9 | input.toggle-all(type="checkbox", ng-checked="TodoList.allChecked", ng-click="TodoList.markAll()") 10 | label(for="toggle-all") Mark all as complete 11 | 12 | ul.todo-list 13 | li( 14 | ng-repeat="todo in TodoList.todos | filter:TodoList.statusFilter track by $index", 15 | ng-class="{completed: todo.completed, editing: todo === TodoList.editedTodo}") 16 | 17 | .view 18 | input.toggle(type="checkbox", ng-model="todo.completed", ng-change="TodoList.statusEdited()") 19 | label(ng-dblclick="TodoList.edit(todo)") {{todo.title}} 20 | button.destroy(ng-click="TodoList.remove(todo)") 21 | 22 | form(ng-submit="TodoList.doneEditing(todo)") 23 | input.edit( 24 | ng-trim="false", 25 | ng-model="todo.title", 26 | ng-blur="TodoList.doneEditing(todo)", 27 | todo-escape="TodoList.revert(todo)", 28 | todo-focus="todo === TodoList.editedTodo") 29 | 30 | footer.footer(ng-show="TodoList.todos.length > 0", ng-cloak) 31 | 32 | span.todo-count 33 | strong {{TodoList.nbRemaining}}  34 | ng-pluralize(count="TodoList.nbRemaining", when="{ one: 'item left', other: 'items left' }") 35 | 36 | ul.filters 37 | li 38 | a(ng-click="TodoList.setFilter('none')", ng-class="{selected: TodoList.statusFilter.completed === undefined}") All 39 | li 40 | a(ng-click="TodoList.setFilter('active')", ng-class="{selected: TodoList.statusFilter.completed === false}") Active 41 | li 42 | a(ng-click="TodoList.setFilter('completed')", ng-class="{selected: TodoList.statusFilter.completed === true}") Completed 43 | 44 | button#clear-completed(ng-click="TodoList.clearCompletedTodos()", ng-show="TodoList.remainingCount < TodoList.todos.length") Clear completed 45 | -------------------------------------------------------------------------------- /example/webpack/modules/todocomponent/templates/todolist.jade: -------------------------------------------------------------------------------- 1 | section.todoapp 2 | 3 | header.header 4 | h1 Todos 5 | form.todo-form(ng-submit="TodoList.add()") 6 | input.new-todo(placeholder="What needs to be done?", ng-model="TodoList.newTodo", autofocus) 7 | 8 | section.main(ng-show="TodoList.todos.length > 0", ng-cloak) 9 | input.toggle-all(type="checkbox", ng-checked="TodoList.allChecked", ng-click="TodoList.markAll()") 10 | label(for="toggle-all") Mark all as complete 11 | 12 | ul.todo-list 13 | li( 14 | ng-repeat="todo in TodoList.todos | filter:TodoList.statusFilter track by $index", 15 | ng-class="{completed: todo.completed, editing: todo === TodoList.editedTodo}") 16 | 17 | .view 18 | input.toggle(type="checkbox", ng-model="todo.completed", ng-change="TodoList.statusEdited()") 19 | label(ng-dblclick="TodoList.edit(todo)") {{todo.title}} 20 | button.destroy(ng-click="TodoList.remove(todo)") 21 | 22 | form(ng-submit="TodoList.doneEditing(todo)") 23 | input.edit( 24 | ng-trim="false", 25 | ng-model="todo.title", 26 | ng-blur="TodoList.doneEditing(todo)", 27 | todo-escape="TodoList.revert(todo)", 28 | todo-focus="todo === TodoList.editedTodo") 29 | 30 | footer.footer(ng-show="TodoList.todos.length > 0", ng-cloak) 31 | 32 | span.todo-count 33 | strong {{TodoList.nbRemaining}}  34 | ng-pluralize(count="TodoList.nbRemaining", when="{ one: 'item left', other: 'items left' }") 35 | 36 | ul.filters 37 | li 38 | a(ng-click="TodoList.setFilter('none')", ng-class="{selected: TodoList.statusFilter.completed === undefined}") All 39 | li 40 | a(ng-click="TodoList.setFilter('active')", ng-class="{selected: TodoList.statusFilter.completed === false}") Active 41 | li 42 | a(ng-click="TodoList.setFilter('completed')", ng-class="{selected: TodoList.statusFilter.completed === true}") Completed 43 | 44 | button#clear-completed(ng-click="TodoList.clearCompletedTodos()", ng-show="TodoList.remainingCount < TodoList.todos.length") Clear completed 45 | -------------------------------------------------------------------------------- /example/es6/modules/todo/controllers/todolist.es6: -------------------------------------------------------------------------------- 1 | (function() { 2 | const {controller, inject} = ngAnnotations; 3 | 4 | @controller('todoCtrl') 5 | @inject('todosFactory', '$filter') 6 | class TodoList { 7 | 8 | statusFilter = {}; 9 | newTodo = ''; 10 | editedTodo = null; 11 | originalTodo = null; 12 | 13 | constructor(todoFactory, $filter) { 14 | this._todoFactory = todoFactory; 15 | this._filter = $filter('filter'); 16 | } 17 | 18 | get todos() { 19 | return this._todoFactory.todos; 20 | } 21 | 22 | get allChecked() { 23 | return this.nbRemaining === 0; 24 | } 25 | 26 | get nbRemaining() { 27 | return this._filter(this.todos, {completed: false}).length; 28 | } 29 | 30 | setFilter(status) { 31 | switch(status) { 32 | case 'active': 33 | this.statusFilter = { completed: false }; 34 | break; 35 | case 'completed': 36 | this.statusFilter = { completed: true }; 37 | break; 38 | default: 39 | this.statusFilter = {}; 40 | break; 41 | } 42 | } 43 | 44 | add() { 45 | var title = this.newTodo.trim(); 46 | if(!title) return; 47 | 48 | this._todoFactory.add(title); 49 | this.newTodo = ''; 50 | } 51 | 52 | edit(todo) { 53 | this.editedTodo = todo; 54 | this.originalTodo = angular.extend({}, todo); 55 | } 56 | 57 | remove(todo) { 58 | this._todoFactory.remove(todo); 59 | } 60 | 61 | doneEditing(todo) { 62 | this.editedTodo = null; 63 | todo.title = todo.title.trim(); 64 | !todo.title && this._todoFactory.remove(todo); 65 | } 66 | 67 | statusEdited() { 68 | this._todoFactory.update(); 69 | } 70 | 71 | revert(todo) { 72 | let index = this.todos.indexOf(todo); 73 | if(!~index) return; 74 | 75 | this.todos[index] = this.originalTodo; 76 | this.doneEditing(this.originalTodo); 77 | } 78 | 79 | markAll() { 80 | let completed = this.allChecked; 81 | this.todos.forEach(todo => todo.completed = !completed); 82 | }; 83 | 84 | } 85 | TodoList.autodeclare('todomvc'); 86 | //angular.module('todomvc').controller(TodoList.$name, TodoList.$component); 87 | })() 88 | 89 | -------------------------------------------------------------------------------- /example/webpack/modules/todocomponent/components/todolist.js: -------------------------------------------------------------------------------- 1 | import {component, inject, attach, conceal} from 'src/app'; 2 | 3 | import todos from '../factories/todos'; 4 | import tplRenderer from '../templates/todolist.jade'; 5 | 6 | const $log = '$log';//service decorated by our log decorator 7 | 8 | @component({ 9 | selector: 'todoList', 10 | alias: 'TodoList', 11 | template: tplRenderer() 12 | }) 13 | @inject('$filter', $log, todos) 14 | export default class TodoList { 15 | 16 | statusFilter = {}; 17 | newTodo = ''; 18 | editedTodo = null; 19 | originalTodo = null; 20 | 21 | @attach(todos) 22 | @conceal 23 | todoFactory; 24 | 25 | @conceal 26 | filter; 27 | 28 | @attach(todos, 'todos') 29 | todos; 30 | 31 | constructor($filter, logger) { 32 | this.filter = $filter('filter'); 33 | logger.special('component initialized'); 34 | } 35 | 36 | get allChecked() { 37 | return this.nbRemaining === 0; 38 | } 39 | 40 | get nbRemaining() { 41 | return this.filter(this.todos, {completed: false}).length; 42 | } 43 | 44 | setFilter(status) { 45 | switch(status) { 46 | case 'active': 47 | this.statusFilter = { completed: false }; 48 | break; 49 | case 'completed': 50 | this.statusFilter = { completed: true }; 51 | break; 52 | default: 53 | this.statusFilter = {}; 54 | break; 55 | } 56 | } 57 | 58 | add() { 59 | var title = this.newTodo.trim(); 60 | if(!title) return; 61 | 62 | this.todoFactory.add(title); 63 | this.newTodo = ''; 64 | } 65 | 66 | edit(todo) { 67 | this.editedTodo = todo; 68 | this.originalTodo = angular.extend({}, todo); 69 | } 70 | 71 | @attach(todos, 'remove') 72 | remove; 73 | 74 | doneEditing(todo) { 75 | this.editedTodo = null; 76 | todo.title = todo.title.trim(); 77 | !todo.title && this.todoFactory.remove(todo); 78 | } 79 | 80 | @attach(todos, 'update') 81 | statusEdited; 82 | 83 | revert(todo) { 84 | let index = this.todos.indexOf(todo); 85 | if(!~index) return; 86 | 87 | this.todos[index] = this.originalTodo; 88 | this.doneEditing(this.originalTodo); 89 | } 90 | 91 | markAll() { 92 | let completed = this.allChecked; 93 | this.todos.forEach(todo => todo.completed = !completed); 94 | }; 95 | 96 | } -------------------------------------------------------------------------------- /webpack/webpack.dev.config.js: -------------------------------------------------------------------------------- 1 | var config = {}; 2 | 3 | /*modules*/ 4 | var Path = require('path'), 5 | webpack = require('webpack'); 6 | 7 | /*consts*/ 8 | var project = Path.join(__dirname, '..'), 9 | wdir = Path.join(project, 'example/webpack'), 10 | entryfile = Path.join(wdir, 'app.js'), 11 | src = Path.join(project, 'src'), 12 | styles = Path.join(wdir, 'styles'), 13 | node_modules = Path.join(project, 'node_modules'), 14 | bower_components = Path.join(project, 'bower_components'); 15 | 16 | /*app folder paths*/ 17 | var modules = Path.join(wdir, 'modules'); 18 | 19 | config.entryfile = entryfile; 20 | config.devtool = 'eval'; 21 | config.entry = {}; 22 | config.entry.vendors = ['angular']; 23 | config.entry.app = [ 24 | 'webpack/hot/dev-server', 25 | entryfile 26 | ]; 27 | 28 | config.cache = true; 29 | 30 | config.output = { 31 | publicPath: '/', 32 | filename: 'app.js', 33 | chunkFilename: '[chunkhash].bundle.js' 34 | }; 35 | 36 | config.resolve = {}; 37 | config.resolve.extensions = ['', '.js', '.jsx', '.scss']; 38 | config.resolve.root = modules; 39 | 40 | config.module = {}; 41 | config.module.loaders = [ 42 | {test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff"}, 43 | {test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff2"}, 44 | {test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream"}, 45 | {test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file"}, 46 | {test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml"}, 47 | {test: /\.jade$/, loader: 'jade'}, 48 | {test: /\.css/, loader: 'style!css'}, 49 | {test: /\.scss$/, loader: 'style!css!sass'}, 50 | { 51 | test: /\.js$/, 52 | loader: 'babel', 53 | query: { 54 | 'experimental': true, 55 | 'optional': ['es7', 'runtime'] 56 | }, 57 | exclude: [node_modules, bower_components] 58 | }, 59 | { 60 | test: /\.jsx$/, 61 | loader: 'babel?experimental&optional=es7', 62 | exclude: [node_modules, bower_components] 63 | } 64 | ]; 65 | 66 | 67 | config.resolve.alias = { 68 | styles: styles, 69 | modules: modules, 70 | bower: bower_components, 71 | npm: node_modules, 72 | src: src, 73 | core: entryfile 74 | }; 75 | 76 | config.plugins = [ 77 | new webpack.optimize.OccurenceOrderPlugin(), 78 | new webpack.optimize.DedupePlugin(), 79 | new webpack.NoErrorsPlugin(), 80 | new webpack.HotModuleReplacementPlugin(), 81 | new webpack.optimize.CommonsChunkPlugin('vendors', 'vendors.js') 82 | ]; 83 | 84 | module.exports = config; -------------------------------------------------------------------------------- /example/es6/modules/todo/templates/todolist.es6: -------------------------------------------------------------------------------- 1 | (function() { 2 | const {run, inject} = ngAnnotations; 3 | 4 | @run() 5 | @inject('$templateCache') 6 | class TemplateRun { 7 | 8 | constructor($templateCache) { 9 | $templateCache.put('todolist.tpl', 10 | `
11 |
12 |

Todos

13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
    21 |
  • 22 |
    23 | 24 | 25 | 26 |
    27 |
    28 | 29 |
    30 |
  • 31 |
32 |
33 |
{{TodoList.nbRemaining}}  34 | 35 | 40 | 41 |
42 |
`); 43 | } 44 | 45 | } 46 | TemplateRun.autodeclare('todomvc'); 47 | })() -------------------------------------------------------------------------------- /src/decorators/utils/attach.js: -------------------------------------------------------------------------------- 1 | import utils from 'src/libs/utils'; 2 | 3 | /** 4 | * @decorator: @attach 5 | * @type: function 6 | * 7 | * replaces the angular dependency attachion system 8 | * 9 | * @param source string component name or this 10 | * @param path (optional) string path toward the property 11 | */ 12 | export default function attach(source = 'this', path = '') { 13 | 14 | if(typeof source !== 'string' 15 | && !(source instanceof Object && '$name' in source)) 16 | throw Error(`the source param of @attach must be a string or an annotated component, ${typeof source} given`) 17 | 18 | if(typeof path !== 'string') 19 | throw Error(`the path param of @attach must be a string, ${typeof path} given`) 20 | 21 | return (prototype, name, descriptor) => { 22 | 23 | if(descriptor instanceof Object 24 | && (descriptor.set !== undefined || descriptor.get !== undefined)) 25 | throw Error(`@attach decorator cannot be applied to an accessor`); 26 | 27 | if(name === undefined) 28 | throw Error(`@attach decorator can only be applied to methods or attributes`); 29 | 30 | descriptor.configurable = true; 31 | 32 | if(source instanceof Object) 33 | source = source.$name; 34 | 35 | let $transformKey = utils.getIdentifier('$transform'); 36 | 37 | if(prototype[$transformKey] === undefined 38 | || !(prototype[$transformKey] instanceof Array)) 39 | prototype[$transformKey] = []; 40 | 41 | let steps = path.split('.'), 42 | propertyName = steps.pop(); 43 | 44 | if(source === 'this') { 45 | delete descriptor.initializer; 46 | delete descriptor.value; 47 | setDescriptor(source,steps,propertyName,descriptor); 48 | } 49 | else 50 | prototype[$transformKey].push(getApplyTransformation(source,steps,propertyName,name)); 51 | } 52 | } 53 | 54 | /** 55 | * @param sourceName String. name of the source component 56 | * @param steps Array. path toward the property 57 | * @param propertyName String. property name 58 | * @param targetName String. name of the target property 59 | * @returns {Function} 60 | */ 61 | function getApplyTransformation(sourceName, steps, propertyName, targetName) { 62 | return function attachTransformation(context, component, injections) { 63 | 64 | let $inject = component.$inject || [], 65 | index = $inject.indexOf(sourceName); 66 | if(!~index) 67 | throw Error(`unable to attach the property ${propertyName}, the component ${sourceName} isn't loaded`) 68 | 69 | let {configurable, enumerable} = Object.getOwnPropertyDescriptor(context, targetName); 70 | let descriptor = {configurable, enumerable}; 71 | setDescriptor(sourceName, steps, propertyName, descriptor, injections[index]); 72 | delete context[targetName]; 73 | Object.defineProperty(context, targetName, descriptor); 74 | } 75 | } 76 | 77 | /** 78 | * @param source Object. source object 79 | * @param steps Array. path toward the property 80 | * @param property String. property name 81 | * @param descriptor Object. property descriptor 82 | * @param context (optional) Object. exec context 83 | */ 84 | function setDescriptor(source, steps, property, descriptor, context = undefined) { 85 | descriptor.get = function() { 86 | if(context === undefined) 87 | context = this; 88 | if(!property) 89 | return context; 90 | let src = getSrc(context, steps); 91 | return src[property] instanceof Function ? src[property].bind(src) : src[property]; 92 | }; 93 | descriptor.set = function(val) { 94 | if(context === undefined) 95 | context = this; 96 | if(!property) 97 | return context; 98 | let src = getSrc(context, steps); 99 | src[property] = val; 100 | }; 101 | } 102 | 103 | 104 | /** 105 | * @param source Object. source object 106 | * @param path Array. path toward the property 107 | */ 108 | function getSrc(source, path = []) { 109 | if(path.length > 0) 110 | for(var i=0; i 0 ? args[1].split(',') : []; 12 | } 13 | 14 | static getUUID(pattern = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx') { 15 | return pattern.replace(/[xy]/g, function(c) { 16 | var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); 17 | return v.toString(16); 18 | }); 19 | } 20 | 21 | static arrayUnique(arr = []) { 22 | var ret = [arr[0]]; 23 | for(var i = 1; i < arr.length; i++) 24 | if(arr[i - 1] !== arr[i]) 25 | ret.push(arr[i]); 26 | return ret; 27 | } 28 | 29 | static getIdentifier(key) { 30 | 31 | if(this.identifiers[key] === undefined) 32 | this.identifiers[key] = window.Symbol ? Symbol(key) : this.getUUID(); 33 | 34 | return this.identifiers[key]; 35 | } 36 | 37 | static addDeclareMethod(target) { 38 | Object.defineProperty(target, 'autodeclare', { 39 | configurable: true, 40 | enumerable: false, 41 | value: function(ngModule) { 42 | let component = this.$component; 43 | if(this.$component instanceof Object && '_$inject' in this.$component) 44 | component = [...this.$component._$inject, this.$component]; 45 | 46 | let params = !!this.$name ? [this.$name, component] : [component]; 47 | 48 | if(typeof ngModule === 'string') 49 | ngModule = angular.module(ngModule); 50 | 51 | return ngModule[this.$type](...params); 52 | } 53 | }); 54 | } 55 | 56 | static applyTransformations(component, instance = {}, injections = []) { 57 | let $transformKey = this.getIdentifier('$transform'), 58 | transformations = component.prototype[$transformKey] || []; 59 | transformations.forEach(transformation => transformation(instance, component, injections)); 60 | } 61 | 62 | static getFinalComponent(target, instance) { 63 | 64 | let $privateKey = this.getIdentifier('$private'), 65 | privateProperties = target.prototype[$privateKey] || []; 66 | 67 | if(privateProperties.length === 0) 68 | return instance; 69 | 70 | privateProperties.push('constructor'); 71 | let prototypeProperties = Object.getOwnPropertyNames(target.prototype), 72 | instanceProperties = Object.getOwnPropertyNames(instance); 73 | 74 | let properties = this.arrayUnique(prototypeProperties.concat(instanceProperties)), 75 | publicProperties = properties.filter(property => !~privateProperties.indexOf(property)), 76 | exposed = {}; 77 | 78 | publicProperties.forEach(property => { 79 | if(instance[property] instanceof Function) { 80 | exposed[property] = (...parameters) => instance[property](...parameters); 81 | Object.defineProperties(exposed[property], { 82 | call: { 83 | value: (scope = instance, ...parameters) => instance[property].apply(scope, parameters), 84 | writable: false, 85 | enumerable: false 86 | }, 87 | apply: { 88 | value: (scope = instance, parameters = []) => instance[property].apply(scope, parameters), 89 | writable: false, 90 | enumerable: false 91 | } 92 | }); 93 | } 94 | else 95 | Object.defineProperty(exposed, property, { 96 | get: () => instance[property], 97 | set: (val) => instance[property] = val, 98 | enumerable: false 99 | }); 100 | }); 101 | 102 | return exposed; 103 | } 104 | 105 | static defineComponent(target, name, type, component) { 106 | 107 | if(!~this.angularComponents.indexOf(type)) 108 | throw Error('the given type must be a valid angular component') 109 | 110 | Object.defineProperties(target, { 111 | '$name': { 112 | value: name !== undefined ? name : target.name, 113 | enumerable: true, 114 | configurable: true 115 | }, 116 | '$type': { 117 | value: type, 118 | enumerable: true, 119 | writable: false 120 | }, 121 | '$component': { 122 | value: component, 123 | enumerable: true, 124 | configurable: true 125 | } 126 | }); 127 | 128 | if(target.$component instanceof Object) 129 | Object.defineProperty(target.$component, '$inject', { 130 | get: () => target.$inject || [], 131 | set: (val) => target.$inject = val 132 | }); 133 | } 134 | } -------------------------------------------------------------------------------- /src/decorators/components/component.js: -------------------------------------------------------------------------------- 1 | import utils from 'src/libs/utils'; 2 | import {inject} from 'src/decorators/utils/inject'; 3 | 4 | /** 5 | * @decorator: @component 6 | * @type: function 7 | * 8 | * declares a new component (directive + controller) 9 | * 10 | * @param options (mandatory) components options 11 | * @paramEx 12 | * { 13 | * selector (string) mandatory - directive name 14 | * alias (string) optional controllerAs option - defaults to selector value 15 | * type (string) optional - restrict directive option 16 | * ioProps (object) optional binded properties - two way binding 17 | * template (string) optional - template string 18 | * templateUrl (string) optional - template url 19 | * transclude (boolean) optional 20 | * lifecycle (object - array of hooks) optional - lifecycle callbacks(compile/prelink/postlink) 21 | * } 22 | * 23 | * @example 24 | * 25 | * @component({ 26 | * selector: 'myComponent', 27 | * alias: '', //optional 28 | * type: 'EA', //optional, defaults to E 29 | * ioProps: { 30 | * 'prop1': 'property1', 31 | * 'prop2': 'property2' 32 | * }, //optional 33 | * template: '', //optional 34 | * //templateUrl: '', //optional 35 | * //transclude: true 36 | * lifecycle: { 37 | * compile: () => {}, 38 | * prelink: () => {}, 39 | * postlink: () => {} 40 | * } //optional 41 | * }) 42 | * export class FooBarComponent { 43 | * 44 | * $ioProps = { 45 | * prop1: null, 46 | * prop2: null 47 | * }; //auto injected by the ioProps component option 48 | * 49 | * } 50 | * 51 | * @returns {Function} 52 | */ 53 | export default function NgComponent(options = {}) { 54 | 55 | let {selector, directive: directiveOpts} = extractComponentOptions(options); 56 | 57 | return (target) => { 58 | 59 | let controller = generateController(target, selector); 60 | directiveOpts.controller = controller.$name; 61 | let directive = generateDirective(selector, directiveOpts); 62 | 63 | 64 | Object.defineProperties(target, { 65 | '$composite': { 66 | value: {controller, directive}, 67 | enumerable: true, 68 | configurable: true 69 | }, 70 | '$type': { 71 | value: 'component', 72 | enumerable: true, 73 | writable: false 74 | }, 75 | 'autodeclare': { 76 | configurable: true, 77 | enumerable: false, 78 | value: function(ngModule) { 79 | let {controller, directive} = this.$composite; 80 | controller.autodeclare(ngModule); 81 | directive.autodeclare(ngModule); 82 | } 83 | } 84 | }); 85 | 86 | } 87 | 88 | } 89 | 90 | function generateController(target, selector) { 91 | 92 | let controller = { 93 | get $inject() { 94 | return target.$inject; 95 | } 96 | }, 97 | controllerName = `${selector}-component-${utils.getUUID()}`; 98 | 99 | var component = function(...injections) { 100 | let instance = new target(...injections); 101 | if(!instance.$ioProps) 102 | instance.$ioProps = {}; 103 | utils.applyTransformations(target, instance, injections); 104 | return utils.getFinalComponent(target, instance); 105 | } 106 | 107 | if(!(target.$inject instanceof Array) || target.$inject.length === 0) { 108 | var parameters = utils.extractParameters(target); 109 | if(parameters.length > 0) 110 | inject(parameters)(component); 111 | } 112 | 113 | utils.addDeclareMethod(controller); 114 | utils.defineComponent(controller, controllerName, 'controller', component); 115 | 116 | return controller; 117 | } 118 | 119 | function generateDirective(selector, options) { 120 | let directive = {}; 121 | utils.addDeclareMethod(directive); 122 | utils.defineComponent(directive, selector, 'directive', () => options); 123 | return directive; 124 | } 125 | 126 | function extractComponentOptions(options = {}) { 127 | 128 | let opts = { 129 | selector: null, 130 | directive: { 131 | restrict: 'E', 132 | scope: {}, 133 | controllerAs: null, 134 | controller: null, 135 | transclude: false 136 | } 137 | }, 138 | hooks = { 139 | compile: () => {}, 140 | prelink: () => {}, 141 | postlink: () => {} 142 | }; 143 | 144 | if(typeof options.selector !== 'string' || !options.selector.length) 145 | throw Error(`@component: the selector option is mandatory and should be a string, ${typeof options.selector} given`); 146 | else opts.selector = opts.directive.controllerAs = options.selector; 147 | 148 | if(typeof options.ioProps === 'object' && !!options.ioProps) { 149 | let props = Object.keys(options.ioProps); 150 | for(let i = 0, length = props.length; i < length; i++) { 151 | let propKey = props[i]; 152 | opts.directive.scope[propKey] = `=${options.ioProps[propKey]}`; 153 | } 154 | } 155 | 156 | if(typeof options.alias === 'string' && options.alias.length > 0) 157 | opts.directive.controllerAs = options.alias; 158 | 159 | if(typeof options.type === 'string' && options.type.length > 0) 160 | opts.directive.restrict = options.type; 161 | 162 | if('template' in options) 163 | opts.directive.template = options.template; 164 | if('templateUrl' in options) 165 | opts.directive.templateUrl = options.templateUrl; 166 | 167 | if('transclude' in options) 168 | opts.directive.transclude = !!options.transclude; 169 | 170 | if(typeof options.lifecycle === 'object' && options.lifecycle) { 171 | 172 | if('compile' in options.lifecycle && typeof options.lifecycle.compile === 'function') 173 | hooks.compile = options.lifecycle.compile; 174 | if('prelink' in options.lifecycle && typeof options.lifecycle.prelink === 'function') 175 | hooks.prelink = options.lifecycle.prelink; 176 | if('postlink' in options.lifecycle && typeof options.lifecycle.postlink === 'function') 177 | hooks.postlink = options.lifecycle.postlink; 178 | 179 | } 180 | 181 | opts.directive.compile = function(...compileArgs) { 182 | hooks.compile.apply(this, ...compileArgs); 183 | return { 184 | pre: function(scope, element, attributes, controller, transcludeFn) { 185 | let ioPropsContainer = {} 186 | if(!controller.$ioProps || typeof controller.$ioProps !== 'object') 187 | controller.$ioProps = ioPropsContainer; 188 | else 189 | ioPropsContainer = controller.$ioProps; 190 | 191 | let props = Object.keys(options.ioProps || []); 192 | props.forEach((propname) => { 193 | Object.defineProperty(ioPropsContainer, propname, { 194 | get() { return scope[propname]; }, 195 | set(val) { scope[propname] = val; } 196 | }); 197 | }); 198 | Object.defineProperty(ioPropsContainer, 'length', { 199 | get() { return props.length; }, 200 | enumerable: true 201 | }); 202 | 203 | hooks.prelink.apply(this, [scope, element, attributes, controller, transcludeFn]) 204 | }, 205 | post: function(...postArgs) { 206 | hooks.postlink.apply(this, postArgs) 207 | } 208 | } 209 | } 210 | 211 | return opts; 212 | } -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | ## 1.1.0 (2016-06-29) 2 | 3 | @decorator: [new feature] 4 | > the decorator decorator is now available. 5 | 6 | ````javascript 7 | import {decorator, inject} from 'node_modules/ng-annotations'; 8 | 9 | @decorator('$log') 10 | @inject('$delegate') 11 | export default class DecoratedLogService { 12 | 13 | /* not necessary if you don't need to override completely the decorated service 14 | @attach('$delegate') 15 | delegate; 16 | */ 17 | 18 | constructor(delegatedService) { 19 | delegatedService.specialLogger = (...data) => this.specialLogger(data); 20 | } 21 | 22 | specialLogger(data) { 23 | console.log('special logger : ', ...data); 24 | } 25 | 26 | /* implicit 27 | $decorate() { 28 | return this.delegate; 29 | } 30 | */ 31 | 32 | } 33 | 34 | ```` 35 | 36 | ````html 37 | 38 | ```` 39 | 40 | ## 1.0.0 (2016-01-19) 41 | 42 | @component: [new feature] 43 | > the component decorator is now available. 44 | 45 | ````javascript 46 | import {component, inject} from 'node_modules/ng-annotations'; 47 | 48 | @component({ 49 | selector: 'myComponent', 50 | alias: 'MyCmp', 51 | type: 'EA', 52 | ioProps: { 53 | name: 'cmpName' 54 | }, 55 | template: ` 56 | 57 | `, 58 | lifecycle: { 59 | compile: () => { console.log('compile time'); }, 60 | prelink: () => { console.log('prelink time'); }, 61 | postlink: () => { console.log('postlink time'); } 62 | } 63 | }) 64 | @inject('$http') 65 | export default class MyComponent { 66 | sayHello() { 67 | console.log(`Hello ${this.$ioProps.name}`); 68 | } 69 | } 70 | 71 | ```` 72 | 73 | ````html 74 | 75 | ```` 76 | 77 | 78 | 79 | ## 0.1.12 (2015-08-20) 80 | 81 | Update: 82 | * @filter 83 | 84 | > the $stateful property is now deprecated 85 | > use the stateful parameter instead 86 | 87 | *before* 88 | 89 | ````javascript 90 | import {filter, inject} from 'node_modules/ng-annotations'; 91 | 92 | @filter('statefulFilter') 93 | @inject('someDependency') 94 | class StatefulFilter { 95 | $stateful = true; 96 | $filter(input) { 97 | return input; //do something with the dependency 98 | } 99 | } 100 | 101 | ```` 102 | 103 | *after* 104 | 105 | ````javascript 106 | import {filter, inject} from 'node_modules/ng-annotations'; 107 | 108 | @filter({name: 'statefulFilter', stateful:true}) 109 | @inject('someDependency') 110 | class StatefulFilter { 111 | $filter(input) { 112 | return input; //do something with the dependency 113 | } 114 | } 115 | 116 | ```` 117 | 118 | 119 | ## 0.1.11 (2015-08-20) 120 | 121 | Feature: 122 | * @filter 123 | 124 | > now supports the stateful filters 125 | 126 | ````javascript 127 | import {filter, inject} from 'node_modules/ng-annotations'; 128 | 129 | @filter('statefulFilter') 130 | @inject('someDependency') 131 | class StatefulFilter { 132 | $stateful = true; 133 | $filter(input) { 134 | return input; //do something with the dependency 135 | } 136 | } 137 | 138 | ```` 139 | 140 | ## 0.1.10 (2015-08-14) 141 | 142 | @conceal: [new feature] 143 | > the conceal decorator is now available. 144 | > it provides a way to declare the class members as private. 145 | 146 | ````javascript 147 | import {factory, inject, conceal} from 'node_modules/ng-annotations'; 148 | 149 | @factory() 150 | @inject('$http') 151 | class MyFactory { 152 | @conceal 153 | @attach('$http') 154 | $http 155 | 156 | @conceal 157 | datas = []; 158 | 159 | getDatas() { 160 | return this.datas; 161 | } 162 | } 163 | 164 | 165 | import {service, inject} from 'node_modules/ng-annotations'; 166 | 167 | @service() 168 | @inject(MyFactory) 169 | class MyService { 170 | 171 | constructor(myFactory) { 172 | myFactory.$http; // not defined 173 | myFactory.datas; // not defined 174 | myFactory.getDatas; // defined 175 | } 176 | 177 | } 178 | 179 | 180 | ```` 181 | 182 | ## 0.1.9 (2015-08-12) 183 | 184 | Bugfix: 185 | * @attach 186 | 187 | > now supports correctly the bindings without second parameter 188 | 189 | ````javascript 190 | import {controller, inject} from 'node_modules/ng-annotations'; 191 | import MyFactory from './myFactory'; 192 | 193 | @controller() 194 | @inject(MyFactory) 195 | export default class MyCtrl { 196 | @attach(MyFactory) 197 | factory; 198 | } 199 | ```` 200 | 201 | ## 0.1.8 (2015-08-12) 202 | 203 | Bugfix: 204 | * @inject 205 | 206 | > now injects correctly the dependencies with the services and providers components 207 | 208 | ## 0.1.7 (2015-08-11) 209 | 210 | @attach [new feature] 211 | > the attach decorator is now available. 212 | > it provides a shortcut to bind references across components. 213 | 214 | Specs: 215 | * binds datas accross components 216 | * not affected by the reference erasing 217 | * keeps the execution context ( autobind like ) 218 | 219 | ````javascript 220 | import {factory, inject} from 'node_modules/ng-annotations'; 221 | 222 | @factory() 223 | @inject('$http') 224 | class MyFactory { 225 | this.datas = []; 226 | load() { 227 | this.$http.get('...').success(dataList => this.datas = dataList) 228 | } 229 | } 230 | ```` 231 | 232 | *before* 233 | ````javascript 234 | import {controller, inject} from 'node_modules/ng-annotations'; 235 | import MyFactory from './myFactory'; 236 | 237 | @controller() 238 | @inject(MyFactory) 239 | export default class MyCtrl { 240 | constructor(factory) { 241 | this.factory = factory; 242 | } 243 | 244 | get datas() { 245 | // thanks to the accessor, you didn't care about reference erasing 246 | return this.factory.datas; 247 | } 248 | set datas(val) { 249 | this.factory.datas = val; 250 | } 251 | } 252 | ```` 253 | 254 | *after* 255 | ````javascript 256 | import {controller, inject} from 'node_modules/ng-annotations'; 257 | import MyFactory from './myFactory'; 258 | 259 | @controller() 260 | @inject(MyFactory) 261 | export default class MyCtrl { 262 | @attach(MyFactory, 'datas') 263 | datas; 264 | } 265 | ```` 266 | 267 | --- 268 | 269 | Breaking changes: 270 | 271 | * @factory 272 | 273 | > in order to decrease the namming issues, the `expose` method is no longer supported. 274 | > use $expose instead. 275 | 276 | *before* 277 | ````javascript 278 | import {factory, autobind} from 'node_modules/ng-annotations'; 279 | 280 | @factory() 281 | export default class MyFactory { 282 | items; 283 | 284 | @autobind 285 | get() { return this.items; } 286 | 287 | @autobind 288 | load(list = []) { this.items = list; } 289 | 290 | expose() { 291 | return { load: this.load, get: this.get } 292 | } 293 | } 294 | ```` 295 | 296 | *after* 297 | ````javascript 298 | import {factory, autobind} from 'node_modules/ng-annotations'; 299 | 300 | @factory() 301 | export default class MyFactory { 302 | items; 303 | 304 | @autobind 305 | get() { return this.items; } 306 | 307 | @autobind 308 | load(list = []) { this.items = list; } 309 | 310 | $expose() { 311 | return { load: this.load, get: this.get } 312 | } 313 | } 314 | ```` 315 | 316 | ## 0.1.6 (2015-08-07) 317 | 318 | @inject 319 | > Now supports the annotated component injections. 320 | > You no longer need to extract the name property to inject a component. 321 | 322 | *before* 323 | ````javascript 324 | import {controller, inject} from 'node_modules/ng-annotations'; 325 | import {$name as yourService} from '../myservice.js'; 326 | 327 | @controller() 328 | @inject(yourService) 329 | export default class MyCtrl { 330 | constructor(yourserv) { 331 | /*do something*/ 332 | } 333 | } 334 | ```` 335 | 336 | *after* 337 | ````javascript 338 | import {controller, inject} from 'node_modules/ng-annotations'; 339 | import yourService from '../myservice.js'; 340 | 341 | @controller() 342 | @inject(yourService) 343 | export default class MyCtrl { 344 | constructor(yourserv) { 345 | /*do something*/ 346 | } 347 | } 348 | ```` 349 | -------------------------------------------------------------------------------- /dist/ng-annotations.js: -------------------------------------------------------------------------------- 1 | !function(e){function t(r){if(n[r])return n[r].exports;var module=n[r]={exports:{},id:r,loaded:!1};return e[r].call(module.exports,module,module.exports,t),module.loaded=!0,module.exports}var n={};return t.m=e,t.c=n,t.p="/",t(0)}([function(module,exports,e){"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var t={};t.controller=e(6),t.component=e(4),t.service=e(12),t.animation=e(3),t.config=e(5),t.directive=e(7),t.factory=e(8),t.filter=e(9),t.provider=e(10),t.run=e(11),t.constant=e(16),t.value=e(17),t.inject=e(2),t.autobind=e(14),t.attach=e(13),t.conceal=e(15),exports["default"]=window.ngAnnotations=t,module.exports=exports["default"]},function(module,exports){"use strict";function e(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(exports,"__esModule",{value:!0});var t=function(){function e(e,t){for(var n=0;n0?n[1].split(","):[]}},{key:"getUUID",value:function(){var e=arguments.length<=0||void 0===arguments[0]?"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx":arguments[0];return e.replace(/[xy]/g,function(e){var t=16*Math.random()|0,n="x"==e?t:3&t|8;return n.toString(16)})}},{key:"arrayUnique",value:function(){for(var e=arguments.length<=0||void 0===arguments[0]?[]:arguments[0],t=[e[0]],n=1;n1?n-1:0),o=1;n>o;o++)r[o-1]=arguments[o];var a=arguments.length<=0||void 0===arguments[0]?t:arguments[0];return t[e].apply(a,r)},writable:!1,enumerable:!1},apply:{value:function(){var n=arguments.length<=0||void 0===arguments[0]?t:arguments[0],r=arguments.length<=1||void 0===arguments[1]?[]:arguments[1];return t[e].apply(n,r)},writable:!1,enumerable:!1}})):Object.defineProperty(u,e,{get:function(){return t[e]},set:function(n){return t[e]=n},enumerable:!1})}),u}},{key:"defineComponent",value:function(e,t,n,r){if(!~this.angularComponents.indexOf(n))throw Error("the given type must be a valid angular component");Object.defineProperties(e,{$name:{value:void 0!==t?t:e.name,enumerable:!0,configurable:!0},$type:{value:n,enumerable:!0,writable:!1},$component:{value:r,enumerable:!0,configurable:!0}}),e.$component instanceof Object&&Object.defineProperty(e.$component,"$inject",{get:function(){return e.$inject||[]},set:function(t){return e.$inject=t}})}},{key:"regexArgs",value:/^function\s*[^\(]*\(\s*([^\)]*)\)/m,enumerable:!0},{key:"regexStripComment",value:/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm,enumerable:!0},{key:"angularComponents",value:["config","run","value","constant","animation","controller","directive","factory","provider","service","filter"],enumerable:!0},{key:"identifiers",value:{},enumerable:!0}]),n}();exports["default"]=n,module.exports=exports["default"]},function(module,exports){"use strict";function e(e){if(!(e instanceof Array)){e=[e];for(var t=arguments.length,n=Array(t>1?t-1:0),r=1;t>r;r++)n[r-1]=arguments[r];n.length>0&&(e=e.concat(n))}return e.forEach(function(t,n){t instanceof Object&&"$name"in t&&(e[n]=t.$name)}),function(t){for(var n=arguments.length,r=Array(n>1?n-1:0),o=1;n>o;o++)r[o-1]=arguments[o];r.length>0&&(t=r[1].value),Object.defineProperty(t,"$inject",{value:e,enumerable:!0,configurable:!0})}}Object.defineProperty(exports,"__esModule",{value:!0}),exports["default"]=e,module.exports=exports["default"]},function(module,exports,e){"use strict";function t(e){return e&&e.__esModule?e:{"default":e}}function n(){var e=arguments.length<=0||void 0===arguments[0]?"":arguments[0];return function(t){e=e||t.name;var n=function(){for(var e=arguments.length,n=Array(e),o=0;e>o;o++)n[o]=arguments[o];var i=new(r.apply(t,[null].concat(n)));return a["default"].applyTransformations(t,i,n),a["default"].getFinalComponent(t,i)};if(!(t.$inject instanceof Array)||0===t.$inject.length){var o=a["default"].extractParameters(t);o.length>0&&(0,l["default"])(o)(n)}a["default"].addDeclareMethod(t),a["default"].defineComponent(t,e,"animation",n)}}Object.defineProperty(exports,"__esModule",{value:!0});var r=Function.prototype.bind;exports["default"]=n;var o=e(1),a=t(o),i=e(2),l=t(i);module.exports=exports["default"]},function(module,exports,e){"use strict";function t(e){return e&&e.__esModule?e:{"default":e}}function n(){var e=arguments.length<=0||void 0===arguments[0]?{}:arguments[0],t=a(e),n=t.selector,i=t.directive;return function(e){var t=r(e,n);i.controller=t.$name;var a=o(n,i);Object.defineProperties(e,{$composite:{value:{controller:t,directive:a},enumerable:!0,configurable:!0},$type:{value:"component",enumerable:!0,writable:!1},autodeclare:{configurable:!0,enumerable:!1,value:function(e){var t=this.$composite,n=t.controller,r=t.directive;n.autodeclare(e),r.autodeclare(e)}}})}}function r(e,t){var n=Object.defineProperties({},{$inject:{get:function(){return e.$inject},configurable:!0,enumerable:!0}}),r=t+"-component-"+u["default"].getUUID(),o=function(){for(var t=arguments.length,n=Array(t),r=0;t>r;r++)n[r]=arguments[r];var o=new(i.apply(e,[null].concat(n)));return o.$ioProps||(o.$ioProps={}),u["default"].applyTransformations(e,o,n),u["default"].getFinalComponent(e,o)};if(!(e.$inject instanceof Array)||0===e.$inject.length){var a=u["default"].extractParameters(e);a.length>0&&(0,f["default"])(a)(o)}return u["default"].addDeclareMethod(n),u["default"].defineComponent(n,r,"controller",o),n}function o(e,t){var n={};return u["default"].addDeclareMethod(n),u["default"].defineComponent(n,e,"directive",function(){return t}),n}function a(){var e=arguments.length<=0||void 0===arguments[0]?{}:arguments[0],t={selector:null,directive:{restrict:"E",scope:{},controllerAs:null,controller:null,transclude:!1}},n={compile:function(){},prelink:function(){},postlink:function(){}};if("string"!=typeof e.selector||!e.selector.length)throw Error("@component: the selector option is mandatory and should be a string, "+typeof e.selector+" given");if(t.selector=t.directive.controllerAs=e.selector,"object"==typeof e.ioProps&&e.ioProps)for(var r=Object.keys(e.ioProps),o=0,a=r.length;a>o;o++){var i=r[o];t.directive.scope[i]="="+e.ioProps[i]}return"string"==typeof e.alias&&e.alias.length>0&&(t.directive.controllerAs=e.alias),"string"==typeof e.type&&e.type.length>0&&(t.directive.restrict=e.type),"template"in e&&(t.directive.template=e.template),"templateUrl"in e&&(t.directive.templateUrl=e.templateUrl),"transclude"in e&&(t.directive.transclude=!!e.transclude),"object"==typeof e.lifecycle&&e.lifecycle&&("compile"in e.lifecycle&&"function"==typeof e.lifecycle.compile&&(n.compile=e.lifecycle.compile),"prelink"in e.lifecycle&&"function"==typeof e.lifecycle.prelink&&(n.prelink=e.lifecycle.prelink),"postlink"in e.lifecycle&&"function"==typeof e.lifecycle.postlink&&(n.postlink=e.lifecycle.postlink)),t.directive.compile=function(){for(var t,r=arguments.length,o=Array(r),a=0;r>a;a++)o[a]=arguments[a];return(t=n.compile).apply.apply(t,[this].concat(o)),{pre:function(t,r,o,a,i){var l={};a.$ioProps&&"object"==typeof a.$ioProps?l=a.$ioProps:a.$ioProps=l;var u=Object.keys(e.ioProps||[]);u.forEach(function(e){Object.defineProperty(l,e,{get:function(){return t[e]},set:function(n){t[e]=n}})}),Object.defineProperty(l,"length",{get:function(){return u.length},enumerable:!0}),n.prelink.apply(this,[t,r,o,a,i])},post:function(){for(var e=arguments.length,t=Array(e),r=0;e>r;r++)t[r]=arguments[r];n.postlink.apply(this,t)}}},t}Object.defineProperty(exports,"__esModule",{value:!0});var i=Function.prototype.bind;exports["default"]=n;var l=e(1),u=t(l),c=e(2),f=t(c);module.exports=exports["default"]},function(module,exports,e){"use strict";function t(e){return e&&e.__esModule?e:{"default":e}}function n(){return function(e){var t=function(){for(var t=arguments.length,n=Array(t),o=0;t>o;o++)n[o]=arguments[o];var i=new(r.apply(e,[null].concat(n)));return a["default"].applyTransformations(e,i,n),i};if(!(e.$inject instanceof Array)||0===e.$inject.length){var n=a["default"].extractParameters(e);n.length>0&&(0,l["default"])(n)(t)}a["default"].addDeclareMethod(e),a["default"].defineComponent(e,null,"config",t)}}Object.defineProperty(exports,"__esModule",{value:!0});var r=Function.prototype.bind;exports["default"]=n;var o=e(1),a=t(o),i=e(2),l=t(i);module.exports=exports["default"]},function(module,exports,e){"use strict";function t(e){return e&&e.__esModule?e:{"default":e}}function n(){var e=arguments.length<=0||void 0===arguments[0]?"":arguments[0];return function(t){e=e||t.name;var n=function(){for(var e=arguments.length,n=Array(e),o=0;e>o;o++)n[o]=arguments[o];var i=new(r.apply(t,[null].concat(n)));return a["default"].applyTransformations(t,i,n),a["default"].getFinalComponent(t,i)};if(!(t.$inject instanceof Array)||0===t.$inject.length){var o=a["default"].extractParameters(t);o.length>0&&(0,l["default"])(o)(n)}a["default"].addDeclareMethod(t),a["default"].defineComponent(t,e,"controller",n)}}Object.defineProperty(exports,"__esModule",{value:!0});var r=Function.prototype.bind;exports["default"]=n;var o=e(1),a=t(o),i=e(2),l=t(i);module.exports=exports["default"]},function(module,exports,e){"use strict";function t(e){return e&&e.__esModule?e:{"default":e}}function n(){var e=arguments.length<=0||void 0===arguments[0]?"":arguments[0];return function(t){e=e||t.name;var n=function(){for(var e=arguments.length,n=Array(e),o=0;e>o;o++)n[o]=arguments[o];var i=new(r.apply(t,[null].concat(n)));return a["default"].applyTransformations(t,i,n),i};if(!(t.$inject instanceof Array)||0===t.$inject.length){var o=a["default"].extractParameters(t);o.length>0&&(0,l["default"])(o)(n)}a["default"].addDeclareMethod(t),a["default"].defineComponent(t,e,"directive",n)}}Object.defineProperty(exports,"__esModule",{value:!0});var r=Function.prototype.bind;exports["default"]=n;var o=e(1),a=t(o),i=e(2),l=t(i);module.exports=exports["default"]},function(module,exports,e){"use strict";function t(e){return e&&e.__esModule?e:{"default":e}}function n(){var e=arguments.length<=0||void 0===arguments[0]?"":arguments[0];return function(t){e=e||t.name;var n=function(){for(var e=arguments.length,n=Array(e),o=0;e>o;o++)n[o]=arguments[o];var i=new(r.apply(t,[null].concat(n)));a["default"].applyTransformations(t,i,n);var l=a["default"].getFinalComponent(t,i);return l.$expose instanceof Function?l.$expose():l};if(!(t.$inject instanceof Array)||0===t.$inject.length){var o=a["default"].extractParameters(t);o.length>0&&(0,l["default"])(o)(n)}a["default"].addDeclareMethod(t),a["default"].defineComponent(t,e,"factory",n)}}Object.defineProperty(exports,"__esModule",{value:!0});var r=Function.prototype.bind;exports["default"]=n;var o=e(1),a=t(o),i=e(2),l=t(i);module.exports=exports["default"]},function(module,exports,e){"use strict";function t(e){return e&&e.__esModule?e:{"default":e}}function n(){var e=arguments.length<=0||void 0===arguments[0]?{name:"",stateful:!1}:arguments[0];return function(t){var n="",o=!1;e instanceof Object?(n=e.name||t.name,o=!!e.stateful):n=e||t.name;var i=function(){function e(){return u.$filter.apply(u,arguments)}for(var n=arguments.length,i=Array(n),l=0;n>l;l++)i[l]=arguments[l];var u=new(r.apply(t,[null].concat(i)));if(!(u.$filter instanceof Function))throw Error('an annotated "filter" must implement the "$filter" method');return a["default"].applyTransformations(t,u,i),u.$stateful===!0&&(console.warn("the $stateful property is deprecated and will be removed in the next versions, use the @filter parameter instead"),console.warn("https://github.com/PillowPillow/ng-annotations#d_filter"),e.$stateful=!0),o&&(e.$stateful=o),e};if(!(t.$inject instanceof Array)||0===t.$inject.length){var u=a["default"].extractParameters(t);u.length>0&&(0,l["default"])(u)(i)}a["default"].addDeclareMethod(t),a["default"].defineComponent(t,n,"filter",i)}}Object.defineProperty(exports,"__esModule",{value:!0});var r=Function.prototype.bind;exports["default"]=n;var o=e(1),a=t(o),i=e(2),l=t(i);module.exports=exports["default"]},function(module,exports,e){"use strict";function t(e){return e&&e.__esModule?e:{"default":e}}function n(){var e=arguments.length<=0||void 0===arguments[0]?"":arguments[0];return function(t){e=e||t.name;var n=function(){for(var e=arguments.length,n=Array(e),o=0;e>o;o++)n[o]=arguments[o];var i=new(r.apply(t,[null].concat(n)));return a["default"].applyTransformations(t,i,n),a["default"].getFinalComponent(t,i)};if(!(t.$inject instanceof Array)||0===t.$inject.length){var o=a["default"].extractParameters(t);o.length>0&&(0,l["default"])(o)(n)}a["default"].addDeclareMethod(t),a["default"].defineComponent(t,e,"provider",n)}}Object.defineProperty(exports,"__esModule",{value:!0});var r=Function.prototype.bind;exports["default"]=n;var o=e(1),a=t(o),i=e(2),l=t(i);module.exports=exports["default"]},function(module,exports,e){"use strict";function t(e){return e&&e.__esModule?e:{"default":e}}function n(){return function(e){var t=function(){for(var t=arguments.length,n=Array(t),o=0;t>o;o++)n[o]=arguments[o];var i=new(r.apply(e,[null].concat(n)));return a["default"].applyTransformations(e,i,n),i};if(!(e.$inject instanceof Array)||0===e.$inject.length){var n=a["default"].extractParameters(e);n.length>0&&(0,l["default"])(n)(t)}a["default"].addDeclareMethod(e),a["default"].defineComponent(e,null,"run",t)}}Object.defineProperty(exports,"__esModule",{value:!0});var r=Function.prototype.bind;exports["default"]=n;var o=e(1),a=t(o),i=e(2),l=t(i);module.exports=exports["default"]},function(module,exports,e){"use strict";function t(e){return e&&e.__esModule?e:{"default":e}}function n(){var e=arguments.length<=0||void 0===arguments[0]?"":arguments[0];return function(t){e=e||t.name;var n=function(){for(var e=arguments.length,n=Array(e),o=0;e>o;o++)n[o]=arguments[o];var i=new(r.apply(t,[null].concat(n)));return a["default"].applyTransformations(t,i,n),a["default"].getFinalComponent(t,i)};if(!(t.$inject instanceof Array)||0===t.$inject.length){var o=a["default"].extractParameters(t);o.length>0&&(0,l["default"])(o)(t)}a["default"].addDeclareMethod(t),a["default"].defineComponent(t,e,"service",n)}}Object.defineProperty(exports,"__esModule",{value:!0});var r=Function.prototype.bind;exports["default"]=n;var o=e(1),a=t(o),i=e(2),l=t(i);module.exports=exports["default"]},function(module,exports,e){"use strict";function t(e){return e&&e.__esModule?e:{"default":e}}function n(){var e=arguments.length<=0||void 0===arguments[0]?"this":arguments[0],t=arguments.length<=1||void 0===arguments[1]?"":arguments[1];if("string"!=typeof e&&!(e instanceof Object&&"$name"in e))throw Error("the source param of @attach must be a string or an annotated component, "+typeof e+" given");if("string"!=typeof t)throw Error("the path param of @attach must be a string, "+typeof t+" given");return function(n,a,i){if(i instanceof Object&&(void 0!==i.set||void 0!==i.get))throw Error("@attach decorator cannot be applied to an accessor");if(void 0===a)throw Error("@attach decorator can only be applied to methods or attributes");i.configurable=!0,e instanceof Object&&(e=e.$name);var u=l["default"].getIdentifier("$transform");void 0!==n[u]&&n[u]instanceof Array||(n[u]=[]);var c=t.split("."),f=c.pop();"this"===e?(delete i.initializer,delete i.value,o(e,c,f,i)):n[u].push(r(e,c,f,a))}}function r(e,t,n,r){return function(a,i,l){var u=i.$inject||[],c=u.indexOf(e);if(!~c)throw Error("unable to attach the property "+n+", the component "+e+" isn't loaded");var f=Object.getOwnPropertyDescriptor(a,r),d=f.configurable,s=f.enumerable,p={configurable:d,enumerable:s};o(e,t,n,p,l[c]),delete a[r],Object.defineProperty(a,r,p)}}function o(e,t,n,r){var o=arguments.length<=4||void 0===arguments[4]?void 0:arguments[4];r.get=function(){if(void 0===o&&(o=this),!n)return o;var e=a(o,t);return e[n]instanceof Function?e[n].bind(e):e[n]},r.set=function(e){if(void 0===o&&(o=this),!n)return o;var r=a(o,t);r[n]=e}}function a(e){var t=arguments.length<=1||void 0===arguments[1]?[]:arguments[1];if(t.length>0)for(var n=0;nInstallation 39 | #### `npm` 40 | `npm install --save ng-annotations` 41 | #### `Bower` 42 | `bower install --save ng-annotations` 43 | 44 | ------------ 45 | ### Basic Usage 46 | > all examples in this repo and below use the [babel-core](https://babeljs.io/) library as transpiler 47 | > you're free to use any other if it supports the es7 decorator feature. 48 | 49 | #### `webpack` 50 | > a configuration example is available in the [webpack dev config](./webpack/webpack.dev.config.js) 51 | 52 | ````javascript 53 | import {service, inject} from 'node_modules/ng-annotations'; 54 | 55 | @service() 56 | @inject('$http') 57 | export default class MyService { 58 | controller($http) { 59 | /*do something*/ 60 | } 61 | } 62 | ```` 63 | 64 | #### `es6 files` 65 | > a configuration example is available in the [gruntfile](./gruntfile.js) 66 | 67 | ````javascript 68 | const {controller, inject} = ngAnnotations; 69 | 70 | @controller('controllerName') 71 | export default class theController { 72 | controller() { 73 | /*do something*/ 74 | } 75 | } 76 | ```` 77 | ------------ 78 | 79 | ### Informations 80 | > All the examples below will show you the webpack way. 81 | > However, an implementation of the angular todolist with the basic es6 syntax is available in the [example/es6](./example/es6) folder 82 | 83 | 84 | ### How it works: 85 | > all component annotations add 3 properties and 1 method to the given class 86 | > `$type`: String. the component type (controller, config, service...). Used by the `autodeclare` method. 87 | > `$name`: String. the component name used by angular. Used by the `autodeclare` method. 88 | > Useful if you want to use the import system with the dependency injection system. 89 | > With this method you'll avoid all hypothetical naming issues. 90 | 91 | ````javascript 92 | /*file1.js*/ 93 | import {service} from 'node_modules/ng-annotations'; 94 | 95 | @service() 96 | export default class MyService {} 97 | 98 | 99 | /*file2.js*/ 100 | import {controller, inject} from 'node_modules/ng-annotations'; 101 | 102 | // import {$name as myService} from './file1'; //before 0.1.6 103 | import myService from './file1'; 104 | 105 | @controller() 106 | @inject(myService) 107 | export default class MyController {} 108 | ```` 109 | 110 | > `$component`: Object. the object/function used by angular, can be different than the original class (function wrap). Used by the `autodeclare` method. 111 | > `autodeclare`: Function(**ngModule**). declares the current component to angular. (replaces the traditional `angular.module('...').controller('name', fn)`) 112 | > the ngModule parameter can be a string (angular module name) or an angular module instance. 113 | 114 | ````javascript 115 | /*autodeclare way*/ 116 | import myService from './file1'; 117 | import myController from './file2'; 118 | 119 | var app = angular.module('app', []); 120 | // useful if you wanna use the import system with the module dependency injection system. 121 | export app.name; 122 | 123 | [myService, myController].forEach(component => component.autodeclare(app)); 124 | 125 | /*alternative*/ 126 | import myService from './file1'; 127 | import myController from './file2'; 128 | 129 | var app = angular.module('app', []); 130 | export app.name; // useful if you wanna use the import system with the module dependency injection system. 131 | 132 | app.service(myService.$name, myService.$component); 133 | app.controller(myController.$name, myController.$component); 134 | 135 | /*without import*/ 136 | import {service} from 'node_modules/ng-annotations'; 137 | 138 | @service() 139 | class MyService {} 140 | 141 | MyService.autodeclare('moduleName'); 142 | ```` 143 | 144 | 145 | ### Available annotations 146 | ------------ 147 | 148 | ## Utils 149 | 150 | ### `@inject` 151 | > The inject annotation replaces the classical array syntax for declare a dependency injection 152 | > Basically, it will feed the $inject property with the list of dependencies 153 | 154 | #### type: *function* 155 | #### target: *class and methods* 156 | #### Params: 157 | - **depsToInject**: String|String[]|Component[]|Component. component(s) to inject 158 | - **...otherDeps**: *(Optional)* ...Strings. 159 | 160 | #### Usage: 161 | ````javascript 162 | import {inject, service} from 'node_modules/ng-annotations'; 163 | import myFactory from '../factory'; 164 | 165 | @service() 166 | @inject('$http','$q',myFactory) // could be @inject(['$http','$q',myFactory]) 167 | export default class CommunicationService { 168 | constructor(http, $q, factory) { 169 | this.http = http; 170 | this.promise = $q; 171 | this.factory = factory; 172 | } 173 | do() {/*do something*/} 174 | } 175 | ```` 176 | 177 | #### Note: 178 | > The implicit dependency injection syntax is also available but shouldn't be used because of minification issues. 179 | 180 | #### Usage: 181 | ````javascript 182 | import {inject, service} from 'node_modules/ng-annotations'; 183 | 184 | @service() 185 | export default class CommunicationService { 186 | constructor($http, $q) { 187 | this.http = $http; 188 | this.promise = $q; 189 | } 190 | do() {/*do something*/} 191 | } 192 | ```` 193 | 194 | ###`@autobind` 195 | > The autobind annotation gives the possibility to bind methods to its current context. 196 | > similar to *object.method.bind(object)* 197 | 198 | #### type: *statement* 199 | #### target: *method only* 200 | #### Usage: 201 | ````javascript 202 | import {service, inject, autobind} from 'node_modules/ng-annotations'; 203 | 204 | @service() 205 | @inject('$timeout') 206 | export default class CommunicationService { 207 | 208 | constructor(timeout) { 209 | this.timeout = timeout; 210 | this.loop(); 211 | } 212 | 213 | @autobind 214 | loop() { 215 | console.log('hello'); 216 | this.timeout(this.loop, 1000); 217 | } 218 | } 219 | ```` 220 | 221 | ###`@attach` 222 | > The attach annotation provides a shortcut to bind references across components and keep them safe. 223 | 224 | #### type: *function* 225 | #### target: *attributes and methods* 226 | #### Params: 227 | - **source** String|Component. source component 228 | - "this" will target the current component 229 | - **path**: *(Optional)* String. path toward the property 230 | - split with dots. `obj.otherObj.myProperty` 231 | 232 | ####Usage: 233 | 234 | ````javascript 235 | // factories/user.js 236 | import {factory, inject} from 'node_modules/ng-annotations'; 237 | 238 | @factory() 239 | @inject('$http') 240 | export default class User { 241 | constructor() { 242 | this.nested.property = 5; 243 | } 244 | connectedUsers = 0; 245 | this.users = []; 246 | load() { 247 | this.$http.get('...').success(userlist => this.users = userlist) 248 | } 249 | } 250 | 251 | 252 | // controller/user.js 253 | import {inject,controller,attach} from 'node_modules/ng-annotations'; 254 | import UserFactory from '../factories/user.js'; 255 | 256 | @controller() 257 | @inject(UserFactory) 258 | class FooBarController { 259 | @attach(UserFactory, 'users') // this.userlist will refers to UserFactory.users 260 | userlist; 261 | 262 | @attach(UserFactory, 'nested.property') 263 | randomProperty; 264 | 265 | @attach(UserFactory, 'load') // same as this.reload = factory.load.bind(factory); 266 | reload; 267 | 268 | clearUsers() { 269 | this.users = []; // update the UserFactory.users property, the reference is kept. 270 | } 271 | } 272 | ```` 273 | 274 | #### Note: 275 | > binded target can be a function, a primitive or an object 276 | 277 | #### Warning: 278 | > The binding occurs after the constructor calling, so you can't use the property at this step. use the controller parameters instead. 279 | 280 | ### `@conceal` 281 | > the conceal decorator provides a way to declare the annotated properties as private 282 | 283 | #### type: *statement* 284 | #### target: *methods and attributes* 285 | #### Usage: 286 | ````javascript 287 | import {factory, inject, attach, conceal} from 'node_modules/ng-annotations'; 288 | 289 | @factory() 290 | @inject('$http') 291 | export default class UserFactory { 292 | 293 | @conceal 294 | @attach('$http') 295 | $http 296 | 297 | @conceal 298 | datas = []; 299 | 300 | constructor(timeout) { 301 | this.datas = [1,2,3]; 302 | } 303 | 304 | method() { 305 | return this.privateMethod(); 306 | } 307 | 308 | @conceal 309 | privateMethod() {} 310 | } 311 | ```` 312 | ------------ 313 | 314 | ## Components 315 | 316 | ###`@controller` 317 | > declare the given class as an angular controller 318 | 319 | #### type: *function* 320 | #### Params: 321 | - **name**: *(Optional)* String. angular controller name, by default the decorator will take the class name. 322 | 323 | #### Usage: 324 | ````javascript 325 | import {controller} from 'node_modules/ng-annotations'; 326 | 327 | @controller('HelloWorld') 328 | export default class MyController { 329 | prop = 0; 330 | } 331 | ```` 332 | 333 | #### Note: 334 | > With this syntax you should always use the controllerAs option and forget $scope (except in certain cases like $watch or $on usages). 335 | 336 | #### Usage: 337 | ````jade 338 | html 339 | head 340 | body 341 | section(ng-controller="HelloWorld as HW") {{HW.prop}} 342 | script(src="app.js") 343 | ```` 344 | 345 | ###`@service` 346 | > declare the given class as an angular service 347 | 348 | #### type: *function* 349 | #### Params: 350 | - **name**: *(Optional)* String. angular service name, by default the decorator will take the class name. 351 | 352 | #### Usage: 353 | ````javascript 354 | import {service} from 'node_modules/ng-annotations'; 355 | 356 | @service('OtherName') 357 | export default class MyService { 358 | method() { return 100 * Math.random()|0 } 359 | } 360 | ```` 361 | 362 | ###`@provider` 363 | > declare the given class as an angular provider 364 | > like the native angular provider you must implement a `$get`. 365 | 366 | #### type: *function* 367 | #### Params: 368 | - **name**: *(Optional)* String. angular provider name, by default the decorator will take the class name. 369 | 370 | #### Usage: 371 | ````javascript 372 | import {provider, inject} from 'node_modules/ng-annotations'; 373 | 374 | @provider() 375 | export default class MyProvider { 376 | @inject($http) 377 | $get($http) {} 378 | } 379 | ```` 380 | 381 | ###`@factory` 382 | > declare the given class as an angular factory 383 | 384 | #### type: *function* 385 | #### Params: 386 | - **name**: *(Optional)* String. angular factory name, by default the decorator will take the class name. 387 | 388 | #### Usage: 389 | ````javascript 390 | import {factory} from 'node_modules/ng-annotations'; 391 | 392 | @factory() 393 | export default class MyFactory { 394 | items; 395 | constructor() { 396 | this.items = []; 397 | } 398 | } 399 | ```` 400 | 401 | > by default the decorator return an instance of the factory class to angular 402 | > so the example above is similar to the following code 403 | 404 | ````javascript 405 | angular.module('...') 406 | .factory('MyFactory', function() { 407 | this.items = []; 408 | return angular.extend(this); 409 | }) 410 | ```` 411 | 412 | > You can change this behaviour by defining an `$expose` method 413 | 414 | ````javascript 415 | import {factory, autobind} from 'node_modules/ng-annotations'; 416 | 417 | @factory() 418 | export default class MyFactory { 419 | items; 420 | 421 | @autobind 422 | get() { 423 | return this.items; 424 | } 425 | 426 | @autobind 427 | load(list = []) { 428 | this.items = list; 429 | } 430 | 431 | $expose() { 432 | return { 433 | load: this.load, 434 | get: this.get 435 | } 436 | } 437 | } 438 | 439 | angular.module('...') 440 | .factory('MyFactory', function() { 441 | this.items = []; 442 | 443 | this.get = function() { return this.items; } 444 | this.load = function(list) { this.items = list || []; } 445 | 446 | return { 447 | get: this.get.bind(this), 448 | load: this.load.bind(this) 449 | } 450 | }) 451 | ```` 452 | 453 | ###`@directive` 454 | > declare the given class as an angular directive 455 | 456 | #### type: *function* 457 | #### Params: 458 | - **name**: *(Optional)* String. angular directive name, by default the decorator will take the class name. 459 | 460 | #### Usage: 461 | ````javascript 462 | import {directive} from 'node_modules/ng-annotations'; 463 | 464 | @directive('myDirective') 465 | export default class MyDirective { 466 | restrict = 'A'; 467 | scope = {}; 468 | link($scope, elem, attr) { 469 | console.log('directive triggered');; 470 | } 471 | } 472 | ```` 473 | 474 | ###`@animation` 475 | > declare the given class as an angular animation 476 | 477 | #### type: *function* 478 | #### Params: 479 | - **selector**: String. css selector. 480 | 481 | #### Usage: 482 | ````javascript 483 | import {animation} from 'node_modules/ng-annotations'; 484 | 485 | @animation('.foobar') 486 | export default class FoobarAnimation { 487 | enter(elem, done) { 488 | elem.css('opacity', 0); 489 | /*do something*/ 490 | } 491 | 492 | leave(elem, done) { 493 | elem.css('opacity', 1); 494 | /*do something*/ 495 | } 496 | } 497 | ```` 498 | 499 | ###`@config` 500 | > declare the given class as an angular config 501 | 502 | #### type: *function* 503 | #### Usage: 504 | ````javascript 505 | import {config, inject} from 'node_modules/ng-annotations'; 506 | 507 | @config() 508 | @inject('$routeProvider') 509 | export default class FooBarConfiguration { 510 | 511 | constructor(routeProvider) { 512 | this.route = routeProvider; 513 | this.setRoutes(); 514 | } 515 | 516 | setRoutes() { 517 | this.route.when('/xxx', { template: '...' }) 518 | } 519 | 520 | } 521 | ```` 522 | 523 | ###`@run` 524 | > declare the given class as an angular run 525 | 526 | #### type: *function* 527 | #### Usage: 528 | ````javascript 529 | import {run, inject} from 'node_modules/ng-annotations'; 530 | 531 | @run() 532 | @inject('myFactory') 533 | export default class SomeRun { 534 | 535 | constructor(myFactory) { 536 | this.fact = myFactory; 537 | this.initModel(); 538 | } 539 | 540 | initModel() { 541 | this.fact.load(); 542 | } 543 | 544 | } 545 | ```` 546 | 547 | ###`@filter` 548 | > declare the given class as an angular filter 549 | 550 | #### type: *function* 551 | #### Params: 552 | - **properties**: *(Optional)* Object|String. angular filter properties. contains the name and the stateful attribute 553 | - name: String. angular filter name, by default the decorator will take the class name. 554 | - stateful: Boolean. default false 555 | 556 | #### Usage: 557 | > The decorated filter is slightly different than the original. 558 | > to make it work you need to implement a `$filter` method. This is the method used by angular. 559 | 560 | ````javascript 561 | import {filter} from 'node_modules/ng-annotations'; 562 | 563 | @filter('capitalizeFilter') 564 | export default class Capitalize { 565 | 566 | toCapitalize(val) { 567 | return val[0].toUpperCase() + val.slice(1); 568 | } 569 | 570 | $filter(val) { 571 | return this.toCapitalize(val); 572 | } 573 | } 574 | ```` 575 | 576 | #### Note: 577 | > If you need to write a stateful filter, you must give a literal objet as parameter to the filter decorator 578 | 579 | ````javascript 580 | //inspired by https://docs.angularjs.org/guide/filter 581 | import {filter, inject, attach} from 'node_modules/ng-annotations'; 582 | 583 | @filter({name:'decorate', stateful:true}) 584 | @inject('decoration') 585 | export default Decorate { 586 | 587 | @attach('decoration', 'symbol') 588 | decorator; 589 | 590 | $filter(input) { 591 | return this.decorator + input + this.decorator; 592 | } 593 | } 594 | ```` 595 | 596 | 597 | ###`@decorator` 598 | > provides a way to decorate an existing angular element 599 | 600 | #### type: *function* 601 | #### Params: 602 | - **name**: String. angular element's name to decorate. 603 | 604 | #### Usage: 605 | ````javascript 606 | import {decorator, inject} from 'node_modules/ng-annotations'; 607 | 608 | @decorator('elementName') 609 | @inject('$delegate') 610 | export default class DecoratedElement { 611 | constructor($delegate) { 612 | /*decoration*/ 613 | } 614 | } 615 | ```` 616 | 617 | > by default the decorator return an instance of $delegate 618 | > so the example above is similar to the following code 619 | 620 | ````javascript 621 | angular.module('...') 622 | .config(function($provide) { 623 | $provide.decorator('elementName', [ 624 | '$delegate', 625 | ($delegate) => { 626 | /*decoration*/ 627 | return $delegate; 628 | } 629 | ]) 630 | }) 631 | ```` 632 | 633 | > You can change this behaviour by defining an `$decorate` method 634 | 635 | ````javascript 636 | import {decorator, inject, attach} from 'node_modules/ng-annotations'; 637 | 638 | @decorator('elementName') 639 | @inject('$delegate') 640 | export default class DecoratedElement { 641 | 642 | //@attach('$delegate') 643 | //$delegate; 644 | 645 | sayHello() { 646 | console.log('hello world'); 647 | } 648 | 649 | $decorate() { 650 | //this.$delegate.sayHello = () => this.sayHello(); 651 | //return this.$delegate; 652 | return this; 653 | } 654 | } 655 | 656 | angular.module('...') 657 | .config(function($provide) { 658 | $provide.decorator('elementName', [ 659 | '$delegate', 660 | ($delegate) => { 661 | return { 662 | sayHello() { 663 | console.log('hello world'); 664 | } 665 | }; 666 | } 667 | ]) 668 | }) 669 | ```` 670 | 671 | 672 | 673 | 674 | ## Wrappers 675 | > the *Value* and *Constant* components can't be replaced by a class. 676 | > In order to simplify their declaration two wrappers are available. 677 | 678 | ###`constant` 679 | #### Params: 680 | - **name**: String. constant name. 681 | - **value**: Mix. constant value. 682 | 683 | #### Usage: 684 | ````javascript 685 | import {constant} from 'node_modules/ng-annotations'; 686 | 687 | export default constant('name', 'a constant'); 688 | ```` 689 | ###`value` 690 | #### Params: 691 | - **name**: String. value name. 692 | - **value**: Mix. value value. 693 | 694 | #### Usage: 695 | ````javascript 696 | import {value} from 'node_modules/ng-annotations'; 697 | 698 | export default value('name', 'a value'); 699 | ```` 700 | 701 | ## Composites 702 | > The composites decorators aren't simple angular component wrappers like above, they implement new concepts on top of Angular 1. 703 | 704 | 705 | ###`@component` 706 | > This decorator declares the given class as a controller and creates an associated directive 707 | 708 | > With the emergence of the new components oriented frameworks like react or angular 2, the application structures changed drastically. 709 | > 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. 710 | > The following decorator combine a controller and a directive in the way of the angular2's @component. 711 | 712 | #### type: *function* 713 | #### Params: 714 | - **options**: *(Mandatory)* Object. component options. 715 | - **selector**: *(Mandatory)* String. directive's name 716 | - **alias**: *(Optional)* String. controllerAs option, defaults to the selector value 717 | - **type**: *(Optional)* String. directive's restrict option, defaults to `E` 718 | - **ioProps**: *(Optional)* Object. the scope properties, all props are bind with the `=` operator (two way binding) 719 | - **template**: *(Optional)* Any. directive's template option 720 | - **templateUrl**: *(Optional)* Any. directive's templateUrl option 721 | - **transclude**: *(Optional)* Boolean. directive's transclude option 722 | - **lifecycle**: *(Optional)* Object array of callbacks, the available hooks are `compile`, `prelink` and `postlink` 723 | 724 | > the component decorator injects a $ioProps property to the working class. It contains the scope properties 725 | 726 | #### Usage: 727 | ````javascript 728 | import {component, inject} from 'node_modules/ng-annotations'; 729 | 730 | @component({ 731 | selector: 'myComponent', 732 | alias: 'MyCmp', 733 | type: 'EA', 734 | ioProps: { 735 | name: 'cmpName' 736 | }, 737 | template: ` 738 | 739 | `, 740 | lifecycle: { 741 | compile: () => { console.log('compile time'); }, 742 | prelink: () => { console.log('prelink time'); }, 743 | postlink: () => { console.log('postlink time'); } 744 | } 745 | }) 746 | @inject('$http') 747 | export default class MyComponent { 748 | sayHello() { 749 | console.log(`Hello ${this.$ioProps.name}`); 750 | } 751 | } 752 | ```` 753 | 754 | ### Modify and build 755 | -------------------- 756 | 757 | `npm install webpack-dev-server -g` 758 | `npm install webpack` 759 | `npm install` 760 | 761 | *Build dist version:* `npm run build` 762 | *Build es6 example:* `npm run es6` 763 | *Start the dev server:* `npm run dev` then go to *http://localhost:8080/webpack-dev-server/* 764 | --------------------------------------------------------------------------------