├── .eslintrc
├── .gitignore
├── LICENSE
├── README.md
├── config.js
├── lib
├── AutoLookupProvider.js
├── DependencyManager.js
├── getUniqueName.js
└── index.js
└── package.json
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "es6": true
4 | },
5 | "ecmaFeatures": {
6 | "forOf": true,
7 | "modules": true
8 | },
9 | "rules": {
10 | "quotes": [1, "single", "avoid-escape"],
11 | "curly": [2, "multi-line"],
12 | "no-loop-func": [0]
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 |
25 | # Dependency directory
26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
27 | node_modules
28 |
29 | # JSPM
30 | jspm_packages
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Teng Yifei
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # angular-es6-di [](http://kasperlewau.github.io/registry/#/?q=angular-es6-di)
2 | Better dependency injection for Angular 1.x, supports services, controllers, directives, and values.
3 |
4 | ## Installation
5 | ```bash
6 | jspm install angular-es6-di
7 | ```
8 | Requires either Traceur or Babel with `es7.decorators` enabled.
9 |
10 | ## Usage
11 | ES6 brings classes with inheritance support. It's nice to write controllers and services in classes and combine subclassing with Angular dependency override. This library lets you do just that. Write a controller like this:
12 | ```js
13 | import { Controller } from 'angular-es6-di';
14 |
15 | @Controller(['$scope'])
16 | export default class MyController {
17 | constructor($scope) {
18 | this.$scope = $scope;
19 | }
20 | }
21 | ```
22 | And to register it with your module, use:
23 | ```js
24 | import MyController from 'MyController';
25 |
26 | let app = angular.module('app', []);
27 | app.use(MyController);
28 | ```
29 | As you can see the dependencies for `MyController` are stated in an array passed to the `@Controller(...)` annotation. If your controller has no dependency, you may write `@Controller` as a short form for `@Controller([])`.
30 |
31 | #### Controller/Service name
32 | `angular-es6-di` lets you do away with magic strings. If you do need to access the raw controller/service name used in registering the class with Angular, use `MyController.$name`.
33 |
34 | #### Compatibility with string-style injections
35 | `angular-es6-di` interoperates well with string-based injections. That means you can refer to your existing services by string and even use mixed string/class dependencies. The same applies to services.
36 | ```js
37 | @Controller([ClassDependency, 'string.dependency'])
38 | ```
39 |
40 | #### Automatic namespacing
41 | The library automatically prefixes your class names with a random string during service/controller registration, thereby eliminating the issue of class name collision.
42 |
43 | ### Services
44 | Sometimes your Angular services follow an interface. Now you'll be able to integrate them into Angular with ease. Suppose you have a logging protocol, it can be written in `angular-es6-di` as:
45 | ```js
46 | import { Service } from 'angular-es6-di';
47 |
48 | @Service
49 | export default class Logger {
50 | log(msg) {
51 | throw new Error('Not implemented');
52 | }
53 | }
54 | ```
55 | And in an implementation:
56 | ```js
57 | import { Service } from 'angular-es6-di';
58 | import Logger from 'Logger';
59 |
60 | @Service
61 | export default class ConsoleLogger extends Logger {
62 | log(msg) {
63 | console.log(msg);
64 | }
65 | }
66 | ```
67 | Note the `extends` statement. All services along the prototype chain will be overriden by this service if it was specified in the `module.use(...)` call, and the override happens in the same order as the order of services in the parameters to `use`. If multiple `use` calls are present, succeeding calls will override previous ones if possible.
68 |
69 | Your other controllers or services can specify a dependency on `Logger`. At runtime an implementation will be chosen based on the `.use(...)` statements in your module.
70 | ```js
71 | import Logger from 'Logger';
72 |
73 | @Controller([Logger])
74 | export default class LoggingController {
75 | constructor(logger) {
76 | this.logger = logger;
77 | }
78 | }
79 | ```
80 | If you have multiple implementations of `Logger`, you'll be able to pick one for use in your app just by changing the parameters to the `use` call.
81 | ```js
82 | import ConsoleLogger from 'ConsoleLogger';
83 | import FileLogger from 'FileLogger';
84 |
85 | let app = angular.module('app', []);
86 | // Pick one :D
87 | // app.use(FileLogger);
88 | app.use(ConsoleLogger);
89 | ```
90 |
91 | ### Values
92 | Value injection is again achieved through ES6 classes. Each value we want to inject corresponds to a different class. By annotating the class you'll be able to use it in services.
93 | ```js
94 | @Value export class UploadURL { }
95 |
96 | @Service([UploadURL])
97 | export class MyUploader {
98 | constructor(url) { ... }
99 | }
100 | ```
101 | The Angular `module.value` method is patched to recognize annotated classes in addition to magic strings.
102 | ```js
103 | import { UploadURL, MyUploader } from 'Uploader';
104 |
105 | let app = angular.module('app', []);
106 | app.use(MyUploader);
107 | app.value(UploadURL, 'www.example.com');
108 | ```
109 | #### Default values
110 | `angular-es6-di` supports default value injection. The syntax is as follows:
111 | ```js
112 | @Value @Default('something') export class MyValue { }
113 | ```
114 | If you do not specify a value for `MyValue` using `module.value`, `MyValue` will automatically take on the value of `'something'` whenever it is required as a dependency in your services/controllers.
115 |
116 | ### Name overriding
117 | If you absolutely need to hardcode the name of a class as it is exposed to Angular, you can override the name using the `@Name` annotation:
118 | ```js
119 | @Controller
120 | @Name('mycontroller')
121 | class NameIsUselessHere {
122 | }
123 | ```
124 | Then you can refer to the controller by the name `mycontroller`.
125 |
126 | ### Directives
127 | You can also declare directives.
128 | ```js
129 | import { Directive } from 'angular-es6-di';
130 |
131 | @Directive
132 | class MyDirective {
133 | constructor() {
134 | this.template = '...';
135 | }
136 | }
137 | ```
138 | Directives enjoy the same support and syntax of dependency injection. Since directives are almost always referred to directly in html, the name of the directive is simply the snake-case version of its class name: `my-directive`, and you would use it like this: `
...
`.
139 |
140 | Directives can also have their names overridden. Just use `@Name`.
141 | ```js
142 | @Directive
143 | @Name('newDirective')
144 | class MyDirective { ... }
145 | ```
146 | ```html
147 | ...
148 | ```
149 |
150 | ## License
151 | MIT
152 |
--------------------------------------------------------------------------------
/config.js:
--------------------------------------------------------------------------------
1 | System.config({
2 | "baseURL": "/",
3 | "defaultJSExtensions": true,
4 | "transpiler": "babel",
5 | "babelOptions": {
6 | "optional": [
7 | "runtime"
8 | ]
9 | },
10 | "paths": {
11 | "github:*": "jspm_packages/github/*",
12 | "npm:*": "jspm_packages/npm/*"
13 | }
14 | });
15 |
16 | System.config({
17 | "map": {
18 | "angular": "github:angular/bower-angular@1.4.2",
19 | "babel": "npm:babel-core@5.6.17",
20 | "babel-runtime": "npm:babel-runtime@5.6.17",
21 | "core-js": "npm:core-js@0.9.18",
22 | "github:jspm/nodelibs-process@0.1.1": {
23 | "process": "npm:process@0.10.1"
24 | },
25 | "npm:babel-runtime@5.6.17": {
26 | "process": "github:jspm/nodelibs-process@0.1.1"
27 | },
28 | "npm:core-js@0.9.18": {
29 | "fs": "github:jspm/nodelibs-fs@0.1.2",
30 | "process": "github:jspm/nodelibs-process@0.1.1",
31 | "systemjs-json": "github:systemjs/plugin-json@0.1.0"
32 | }
33 | }
34 | });
35 |
36 |
--------------------------------------------------------------------------------
/lib/AutoLookupProvider.js:
--------------------------------------------------------------------------------
1 | import DI from './DependencyManager';
2 |
3 | /**
4 | * Provides a service by looking up its implementations.
5 | */
6 | export default class AutoLookupProvider {
7 | constructor(module, clazz) {
8 | this.module = module;
9 | this.Class = clazz;
10 | this.instance = null;
11 | this.$get.$inject = ['$injector'];
12 | }
13 |
14 | $get($injector) {
15 | //console.log('Get ' + this.Class.$name + ' in ' + this.module.name);
16 | let Impl = DI.findImplementation(this.Class);
17 | if (Impl.$name === this.Class.$name) {
18 | //console.log('Instantiating ' + Impl.$name);
19 | if (this.instance) return this.instance;
20 | this.instance = $injector.get(Impl.$internal);
21 | return this.instance;
22 | } else {
23 | //console.log('Delegating to ' + Impl.$name);
24 | return $injector.get(Impl.$name);
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/lib/DependencyManager.js:
--------------------------------------------------------------------------------
1 | import angular from 'angular';
2 | import getUniqueName from './getUniqueName';
3 |
4 | let dependencyMap = new Map();
5 | let defaultModule = angular.module(getUniqueName(), []);
6 |
7 | export default class DependencyManager {
8 | static findImplementation(clazz) {
9 | return dependencyMap.get(clazz);
10 | }
11 |
12 | static getDefaultModuleName() {
13 | return defaultModule.name;
14 | }
15 |
16 | static bind(iface) {
17 | return {
18 | to: impl => dependencyMap.set(iface, impl),
19 | toValue: val => defaultModule.config(['$provide', x => x.value(iface.$name, val)])
20 | };
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lib/getUniqueName.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Gets a unique name for Angular services and controllers.
3 | */
4 | export default function getUniqueName () {
5 | let text = [];
6 | let possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
7 |
8 | for (let i = 0; i < 14; i++) {
9 | text.push(possible.charAt(Math.floor(Math.random() * possible.length)));
10 | }
11 | return '[' + text.join('') + ']';
12 | }
13 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | import angular from 'angular';
2 | import getUniqueName from './getUniqueName';
3 | import DI from './DependencyManager';
4 | import Provider from './AutoLookupProvider';
5 |
6 | let stringOfClass = x => typeof x === 'function' ? x.$name : x;
7 | let crawlPrototypes = clazz => {
8 | let classes = [];
9 | while (clazz.$name) {
10 | classes.push(clazz);
11 | clazz = Object.getPrototypeOf(clazz);
12 | }
13 | return classes;
14 | };
15 |
16 | export let Name = name => clazz => { clazz.$name = name; return clazz; };
17 |
18 | export function Service(input = []) {
19 | let func = dependencies => clazz => {
20 | clazz.$name = clazz.$name || getUniqueName() + clazz.name; // name leading to a proxy provider
21 | clazz.$internal = getUniqueName(); // name that actually leads to the instance
22 |
23 | clazz.$inject = dependencies.map(stringOfClass);
24 | clazz.$overrides = crawlPrototypes(clazz);
25 | clazz.$applyOverride = module => {
26 | clazz.$overrides.forEach(iface => {
27 | DI.bind(iface).to(clazz);
28 | // Register every interface/implementation in angular
29 | // Will figure out exact implementation later
30 | module.provider(iface.$name, new Provider(module, iface));
31 | });
32 | // Register real instance with internal name
33 | module.service(clazz.$internal, clazz);
34 | };
35 | clazz.$registerOn = clazz.$applyOverride;
36 | return clazz;
37 | };
38 | // Unwrapping
39 | if (input.constructor !== Array) {
40 | return func([])(input);
41 | } else {
42 | return func(input);
43 | }
44 | }
45 |
46 | export let Default = defVal => clazz => { clazz.$default = defVal; return clazz; };
47 |
48 | export function Value(clazz) {
49 | clazz.$name = clazz.$name || getUniqueName() + clazz.name;
50 | if (clazz.$default) DI.bind(clazz).toValue(clazz.$default);
51 | }
52 |
53 | export function Controller(input = []) {
54 | let func = dependencies => clazz => {
55 | clazz.$name = clazz.$name || getUniqueName() + clazz.name;
56 | clazz.$inject = dependencies.map(stringOfClass);
57 | clazz.$registerOn = module =>
58 | module.config(['$controllerProvider', x => x.register(clazz.$name, clazz)]);
59 | return clazz;
60 | };
61 | // Unwrapping
62 | if (input.constructor !== Array) {
63 | return func([])(input);
64 | } else {
65 | return func(input);
66 | }
67 | }
68 |
69 | export let Inject = deps => (x, y, desc) => {
70 | desc.value.$inject = deps.map(stringOfClass);
71 | return desc;
72 | };
73 |
74 | export function Directive(input = []) {
75 | let func = dependencies => clazz => {
76 | clazz.$name = clazz.$name || clazz.name;
77 | clazz.$inject = dependencies.map(stringOfClass);
78 | clazz.$registerOn = module =>
79 | module.directive(clazz.$name, ['$injector', x => x.instantiate(clazz)]);
80 | // Provide lexical binding if compile() returns a function
81 | if (clazz.prototype.compile) {
82 | let originalCompile = clazz.prototype.compile;
83 | clazz.prototype.compile = function () {
84 | let retn = originalCompile.apply(this, arguments);
85 | if (typeof retn === 'function') retn = retn.bind(this);
86 | return retn;
87 | };
88 | }
89 | };
90 | // Unwrapping
91 | if (input.constructor !== Array) {
92 | return func([])(input);
93 | } else {
94 | return func(input);
95 | }
96 | }
97 |
98 | // Extend Angular modules with `.use`
99 | let origModuleCall = angular.module;
100 | angular.module = function () {
101 | let module = origModuleCall.apply(angular, arguments);
102 | module.use = (...components) => {
103 | components.forEach(component => component.$registerOn(module));
104 | return module;
105 | };
106 | // Add in our module for default values
107 | module.requires.push(DI.getDefaultModuleName());
108 | // Extend Angular `.value` calls to process annotated values
109 | let origValueCall = module.value;
110 | module.value = function () {
111 | // Calling with annotated value class?
112 | if (arguments[0].constructor !== String) arguments[0] = arguments[0].$name;
113 | return origValueCall.apply(module, arguments);
114 | };
115 | return module;
116 | };
117 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-es6-di",
3 | "description": "Better dependency injection for Angular 1.x, supports services, controllers, directives, and values.",
4 | "version": "1.0.5",
5 | "main": "lib/index.js",
6 | "directories": {
7 | "test": "tests"
8 | },
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/tengyifei/angular-es6-di.git"
15 | },
16 | "keywords": [
17 | "angular",
18 | "es6",
19 | "dependency",
20 | "injection"
21 | ],
22 | "author": "Yifei Teng (https://thinkingandcomputing.com/)",
23 | "license": "MIT",
24 | "bugs": {
25 | "url": "https://github.com/tengyifei/angular-es6-di/issues"
26 | },
27 | "homepage": "https://github.com/tengyifei/angular-es6-di#readme",
28 | "jspm": {
29 | "registry": "jspm",
30 | "main": "index.js",
31 | "format": "es6",
32 | "directories": {
33 | "lib": "lib"
34 | },
35 | "dependencies": {
36 | "angular": "github:angular/bower-angular@^1.4.2"
37 | },
38 | "devDependencies": {
39 | "babel": "npm:babel-core@^5.6.4",
40 | "babel-runtime": "npm:babel-runtime@^5.6.4",
41 | "core-js": "npm:core-js@^0.9.17"
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------