From b8135236513d8f817b088464ee370dd930e721c6 Mon Sep 17 00:00:00 2001 From: "Remo H. Jansen" Date: Mon, 18 Dec 2017 00:47:57 +0000 Subject: [PATCH] Implements `toService` support (#583) (#720) * Implemented #583 * Added docs --- package.json | 2 +- src/interfaces/interfaces.ts | 1 + src/inversify.ts | 1 + src/syntax/binding_to_syntax.ts | 6 ++ src/utils/binding_utils.ts | 6 ++ test/features/transitive_bindings.test.ts | 88 +++++++++++++++++++++++ wiki/readme.md | 87 +++++++++++----------- wiki/transitive_bindings.md | 60 ++++++++++++++++ 8 files changed, 209 insertions(+), 42 deletions(-) create mode 100644 src/utils/binding_utils.ts create mode 100644 test/features/transitive_bindings.test.ts create mode 100644 wiki/transitive_bindings.md diff --git a/package.json b/package.json index b2a5f7d9a..1e0cb81d4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inversify", - "version": "4.8.0", + "version": "4.9.0", "description": "A powerful and lightweight inversion of control container for JavaScript and Node.js apps powered by TypeScript.", "main": "lib/inversify.js", "jsnext:main": "es/inversify.js", diff --git a/src/interfaces/interfaces.ts b/src/interfaces/interfaces.ts index 4107f6f53..f7eb1d1d2 100644 --- a/src/interfaces/interfaces.ts +++ b/src/interfaces/interfaces.ts @@ -263,6 +263,7 @@ namespace interfaces { toFunction(func: T): BindingWhenOnSyntax; toAutoFactory(serviceIdentifier: ServiceIdentifier): BindingWhenOnSyntax; toProvider(provider: ProviderCreator): BindingWhenOnSyntax; + toService(service: ServiceIdentifier): void; } export interface ConstraintFunction extends Function { diff --git a/src/inversify.ts b/src/inversify.ts index 5c9124bd7..a407a0e29 100644 --- a/src/inversify.ts +++ b/src/inversify.ts @@ -18,3 +18,4 @@ export { interfaces } from "./interfaces/interfaces"; export { decorate } from "./annotation/decorator_utils"; export { traverseAncerstors, taggedConstraint, namedConstraint, typeConstraint } from "./syntax/constraint_helpers"; export { getServiceIdentifierAsString } from "./utils/serialization"; +export { multiBindToService } from "./utils/binding_utils"; diff --git a/src/syntax/binding_to_syntax.ts b/src/syntax/binding_to_syntax.ts index b25debe4e..96ca8d1d3 100644 --- a/src/syntax/binding_to_syntax.ts +++ b/src/syntax/binding_to_syntax.ts @@ -77,6 +77,12 @@ class BindingToSyntax implements interfaces.BindingToSyntax { return new BindingWhenOnSyntax(this._binding); } + public toService(service: string | symbol | interfaces.Newable | interfaces.Abstract): void { + this.toDynamicValue( + (context) => context.container.get(service) + ); + } + } export { BindingToSyntax }; diff --git a/src/utils/binding_utils.ts b/src/utils/binding_utils.ts new file mode 100644 index 000000000..047f621ad --- /dev/null +++ b/src/utils/binding_utils.ts @@ -0,0 +1,6 @@ +import { interfaces } from "../inversify"; + +export const multiBindToService = (container: interfaces.Container) => + (service: interfaces.ServiceIdentifier) => + (...types: interfaces.ServiceIdentifier[]) => + types.forEach((t) => container.bind(t).toService(service)); diff --git a/test/features/transitive_bindings.test.ts b/test/features/transitive_bindings.test.ts new file mode 100644 index 000000000..a3ca4bef6 --- /dev/null +++ b/test/features/transitive_bindings.test.ts @@ -0,0 +1,88 @@ +import { expect } from "chai"; +import { Container, injectable, multiBindToService } from "../../src/inversify"; + +describe("Transitive bindings", () => { + + it("Should be able to bind to a service", () => { + + @injectable() + class MySqlDatabaseTransactionLog { + public time: number; + public name: string; + public constructor() { + this.time = new Date().getTime(); + this.name = "MySqlDatabaseTransactionLog"; + } + } + + @injectable() + class DatabaseTransactionLog { + public time: number; + public name: string; + } + + @injectable() + class TransactionLog { + public time: number; + public name: string; + } + + const container = new Container(); + container.bind(MySqlDatabaseTransactionLog).toSelf().inSingletonScope(); + container.bind(DatabaseTransactionLog).toService(MySqlDatabaseTransactionLog); + container.bind(TransactionLog).toService(DatabaseTransactionLog); + + const mySqlDatabaseTransactionLog = container.get(MySqlDatabaseTransactionLog); + const databaseTransactionLog = container.get(DatabaseTransactionLog); + const transactionLog = container.get(TransactionLog); + + expect(mySqlDatabaseTransactionLog.name).to.eq("MySqlDatabaseTransactionLog"); + expect(databaseTransactionLog.name).to.eq("MySqlDatabaseTransactionLog"); + expect(transactionLog.name).to.eq("MySqlDatabaseTransactionLog"); + expect(mySqlDatabaseTransactionLog.time).to.eq(databaseTransactionLog.time); + expect(databaseTransactionLog.time).to.eq(transactionLog.time); + + }); + + it("Should be able to bulk bind to a service", () => { + + @injectable() + class MySqlDatabaseTransactionLog { + public time: number; + public name: string; + public constructor() { + this.time = new Date().getTime(); + this.name = "MySqlDatabaseTransactionLog"; + } + } + + @injectable() + class DatabaseTransactionLog { + public time: number; + public name: string; + } + + @injectable() + class TransactionLog { + public time: number; + public name: string; + } + + const container = new Container(); + const mbts = multiBindToService(container); + container.bind(MySqlDatabaseTransactionLog).toSelf().inSingletonScope(); + mbts(MySqlDatabaseTransactionLog)(DatabaseTransactionLog, TransactionLog); + + const mySqlDatabaseTransactionLog = container.get(MySqlDatabaseTransactionLog); + const databaseTransactionLog = container.get(DatabaseTransactionLog); + const transactionLog = container.get(TransactionLog); + + expect(mySqlDatabaseTransactionLog.name).to.eq("MySqlDatabaseTransactionLog"); + expect(databaseTransactionLog.name).to.eq("MySqlDatabaseTransactionLog"); + expect(transactionLog.name).to.eq("MySqlDatabaseTransactionLog"); + expect(mySqlDatabaseTransactionLog.time).to.eq(databaseTransactionLog.time); + expect(databaseTransactionLog.time).to.eq(transactionLog.time); + + }); + +}); diff --git a/wiki/readme.md b/wiki/readme.md index c7a208651..29b567a01 100644 --- a/wiki/readme.md +++ b/wiki/readme.md @@ -1,47 +1,52 @@ # Wiki + Welcome to the InversifyJS wiki! -### Installation and environment support -- [Installation](https://github.com/inversify/InversifyJS/blob/master/wiki/installation.md) -- [Environment support and polyfills](https://github.com/inversify/InversifyJS/blob/master/wiki/environment.md) -- [Vanilla JavaScript example](https://github.com/inversify/InversifyJS/blob/master/wiki/basic_js_example.md) -- [ES5 and ES6 support](https://github.com/inversify/InversifyJS/blob/master/wiki/javascript.md) -- [Upgrade guide](https://github.com/inversify/InversifyJS/blob/master/wiki/upgrade.md) +## Installation and environment support + +- [Installation](./installation.md) +- [Environment support and polyfills](./environment.md) +- [Vanilla JavaScript example](./basic_js_example.md) +- [ES5 and ES6 support](./javascript.md) +- [Upgrade guide](./upgrade.md) + +## The InversifyJS Features and API + +- [Support for classes](./classes_as_id.md) +- [Support for Symbols](./symbols_as_id.md) +- [Container API](./container_api.md) +- [Declaring container modules](./container_modules.md) +- [Container snapshots](./container_snapshots.md) +- [Controlling the scope of the dependencies](./scope.md) +- [Declaring optional dependencies](./optional_dependencies.md) +- [Injecting a constant or dynamic value](./value_injection.md) +- [Injecting a class constructor](./constructor_injection.md) +- [Injecting a Factory](./factory_injection.md) +- [Auto factory](./auto_factory.md) +- [Injecting a Provider (asynchronous Factory)](./provider_injection.md) +- [Activation handler](./activation_handler.md) +- [Post Construct decorator](./post_construct.md) +- [Middleware](./middleware.md) +- [Multi-injection](./multi_injection.md) +- [Tagged bindings](./tagged_bindings.md) +- [Create your own tag decorators](./custom_tag_decorators.md) +- [Named bindings](./named_bindings.md) +- [Default target](./default_targets.md) +- [Support for hierarchical DI systems](./hierarchical_di.md) +- [Contextual bindings & @targetName](./contextual_bindings.md) +- [Property injection](./property_injection.md) +- [Circular dependencies](./circular_dependencies.md) +- [Inheritance](./inheritance.md) +- [Transitive bindings](./transitive_bindings.md) -### The InversifyJS Features and API -- [Support for classes](https://github.com/inversify/InversifyJS/blob/master/wiki/classes_as_id.md) -- [Support for Symbols](https://github.com/inversify/InversifyJS/blob/master/wiki/symbols_as_id.md) -- [Container API](https://github.com/inversify/InversifyJS/blob/master/wiki/container_api.md) -- [Declaring container modules](https://github.com/inversify/InversifyJS/blob/master/wiki/container_modules.md) -- [Container snapshots](https://github.com/inversify/InversifyJS/blob/master/wiki/container_snapshots.md) -- [Controlling the scope of the dependencies](https://github.com/inversify/InversifyJS/blob/master/wiki/scope.md) -- [Declaring optional dependencies](https://github.com/inversify/InversifyJS/blob/master/wiki/optional_dependencies.md) -- [Injecting a constant or dynamic value](https://github.com/inversify/InversifyJS/blob/master/wiki/value_injection.md) -- [Injecting a class constructor](https://github.com/inversify/InversifyJS/blob/master/wiki/constructor_injection.md) -- [Injecting a Factory](https://github.com/inversify/InversifyJS/blob/master/wiki/factory_injection.md) -- [Auto factory](https://github.com/inversify/InversifyJS/blob/master/wiki/auto_factory.md) -- [Injecting a Provider (asynchronous Factory)](https://github.com/inversify/InversifyJS/blob/master/wiki/provider_injection.md) -- [Activation handler](https://github.com/inversify/InversifyJS/blob/master/wiki/activation_handler.md) -- [Post Construct decorator](https://github.com/inversify/InversifyJS/blob/master/wiki/post_construct.md) -- [Middleware](https://github.com/inversify/InversifyJS/blob/master/wiki/middleware.md) -- [Multi-injection](https://github.com/inversify/InversifyJS/blob/master/wiki/multi_injection.md) -- [Tagged bindings](https://github.com/inversify/InversifyJS/blob/master/wiki/tagged_bindings.md) -- [Create your own tag decorators](https://github.com/inversify/InversifyJS/blob/master/wiki/custom_tag_decorators.md) -- [Named bindings](https://github.com/inversify/InversifyJS/blob/master/wiki/named_bindings.md) -- [Default target](https://github.com/inversify/InversifyJS/blob/master/wiki/default_targets.md) -- [Support for hierarchical DI systems](https://github.com/inversify/InversifyJS/blob/master/wiki/hierarchical_di.md) -- [Contextual bindings & @targetName](https://github.com/inversify/InversifyJS/blob/master/wiki/contextual_bindings.md) -- [Property injection](https://github.com/inversify/InversifyJS/blob/master/wiki/property_injection.md) -- [Circular dependencies](https://github.com/inversify/InversifyJS/blob/master/wiki/circular_dependencies.md) -- [Inheritance](https://github.com/inversify/InversifyJS/blob/master/wiki/inheritance.md) +## Other documents -### Other documents -- [Why InversifyJS?](https://github.com/inversify/InversifyJS/blob/master/wiki/purpose.md) -- [Object-oriented design](https://github.com/inversify/InversifyJS/blob/master/wiki/oo_design.md) -- [Good practices](https://github.com/inversify/InversifyJS/blob/master/wiki/good_practices.md) -- [Ecosystem](https://github.com/inversify/InversifyJS/blob/master/wiki/ecosystem.md) -- [Recipes](https://github.com/inversify/InversifyJS/blob/master/wiki/recipes.md) -- [Injecting npm modules](https://github.com/inversify/InversifyJS/blob/master/wiki/injecting_npm_modules.md) -- [Architecture overview](https://github.com/inversify/InversifyJS/blob/master/wiki/architecture.md) -- [Glossary](https://github.com/inversify/InversifyJS/blob/master/wiki/glossary.md) +- [Why InversifyJS?](./purpose.md) +- [Object-oriented design](./oo_design.md) +- [Good practices](./good_practices.md) +- [Ecosystem](./ecosystem.md) +- [Recipes](./recipes.md) +- [Injecting npm modules](./injecting_npm_modules.md) +- [Architecture overview](./architecture.md) +- [Glossary](./glossary.md) - [Roadmap](https://github.com/inversify/InversifyJS/milestones) diff --git a/wiki/transitive_bindings.md b/wiki/transitive_bindings.md new file mode 100644 index 000000000..25d7e6d0b --- /dev/null +++ b/wiki/transitive_bindings.md @@ -0,0 +1,60 @@ +# Transitive bindings + +A transitive type binding allows as to declare a type binding that is resolved by a prviously declared type binding. + +A transitive binding can be declared using the `toService` method: + +```ts +@injectable() +class MySqlDatabaseTransactionLog { + public time: number; + public name: string; + public constructor() { + this.time = new Date().getTime(); + this.name = "MySqlDatabaseTransactionLog"; + } +} + +@injectable() +class DatabaseTransactionLog { + public time: number; + public name: string; +} + +@injectable() +class TransactionLog { + public time: number; + public name: string; +} + +const container = new Container(); +container.bind(MySqlDatabaseTransactionLog).toSelf().inSingletonScope(); +container.bind(DatabaseTransactionLog).toService(MySqlDatabaseTransactionLog); +container.bind(TransactionLog).toService(DatabaseTransactionLog); + +const mySqlDatabaseTransactionLog = container.get(MySqlDatabaseTransactionLog); +const databaseTransactionLog = container.get(DatabaseTransactionLog); +const transactionLog = container.get(TransactionLog); + +expect(mySqlDatabaseTransactionLog.name).to.eq("MySqlDatabaseTransactionLog"); +expect(databaseTransactionLog.name).to.eq("MySqlDatabaseTransactionLog"); +expect(transactionLog.name).to.eq("MySqlDatabaseTransactionLog"); +expect(mySqlDatabaseTransactionLog.time).to.eq(databaseTransactionLog.time); +expect(databaseTransactionLog.time).to.eq(transactionLog.time); +``` + +There is also an utility function named `multiBindToService` which allows us to declare multiple transitive bindings in one go. + +For example, instead of writing the following: + +```ts +container.bind(DatabaseTransactionLog).toService(MySqlDatabaseTransactionLog); +container.bind(TransactionLog).toService(DatabaseTransactionLog); +``` + +We can use `multiBindToService` to write the following: + +```ts +multiBindToService(container)(MySqlDatabaseTransactionLog) + (DatabaseTransactionLog, TransactionLog); +```