├── 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 | ``);
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 |
--------------------------------------------------------------------------------