From 3ab12255a44c819d52a8ba83ae95271de524a9ed Mon Sep 17 00:00:00 2001 From: "Remo H. Jansen" Date: Tue, 7 Feb 2017 22:31:17 +0000 Subject: [PATCH] Implements container.rebind support #484 (#490) * Implemented container.rebind support #484 * Prevent one test from running in browsers --- package.json | 4 +-- src/container/container.ts | 25 +++++++++++++- src/container/container_module.ts | 4 +-- src/interfaces/interfaces.ts | 16 ++++++++- test/container/container.test.ts | 21 ++++++++++++ test/container/container_module.test.ts | 42 +++++++++++++++++++++++ test/inversify.test.ts | 44 ------------------------- test/node/error_messages.test.ts | 44 +++++++++++++++++++++++++ wiki/container_api.md | 44 ++++++++++++++++++++++--- wiki/container_modules.md | 16 ++++++--- 10 files changed, 201 insertions(+), 59 deletions(-) diff --git a/package.json b/package.json index 37dbca9ac..9abd02a2e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inversify", - "version": "3.0.0", + "version": "3.1.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", @@ -72,7 +72,7 @@ "run-sequence": "^1.2.0", "sinon": "^1.17.3", "tslint": "^4.4.2", - "typescript": "^2.1.5", + "typescript": "^2.2.0", "vinyl-buffer": "^1.0.0", "vinyl-source-stream": "^1.1.0" } diff --git a/src/container/container.ts b/src/container/container.ts index df7f1c165..c0e1eb877 100644 --- a/src/container/container.ts +++ b/src/container/container.ts @@ -108,11 +108,29 @@ class Container implements interfaces.Container { }; }; + let getRebindFunction = (moduleId: string) => { + return (serviceIdentifier: interfaces.ServiceIdentifier) => { + let _rebind = this.rebind.bind(this); + let bindingToSyntax = _rebind(serviceIdentifier); + setModuleId(bindingToSyntax, moduleId); + return bindingToSyntax; + }; + }; + modules.forEach((module) => { + let bindFunction = getBindFunction(module.guid); let unbindFunction = getUnbindFunction(module.guid); let isboundFunction = getIsboundFunction(module.guid); - module.registry(bindFunction, unbindFunction, isboundFunction); + let rebindFunction = getRebindFunction(module.guid); + + module.registry( + bindFunction, + unbindFunction, + isboundFunction, + rebindFunction + ); + }); } @@ -139,6 +157,11 @@ class Container implements interfaces.Container { return new BindingToSyntax(binding); } + public rebind(serviceIdentifier: interfaces.ServiceIdentifier): interfaces.BindingToSyntax { + this.unbind(serviceIdentifier); + return this.bind(serviceIdentifier); + } + // Removes a type binding from the registry by its key public unbind(serviceIdentifier: interfaces.ServiceIdentifier): void { try { diff --git a/src/container/container_module.ts b/src/container/container_module.ts index 14c6ad64c..54b2c3a7a 100644 --- a/src/container/container_module.ts +++ b/src/container/container_module.ts @@ -4,9 +4,9 @@ import { guid } from "../utils/guid"; class ContainerModule implements interfaces.ContainerModule { public guid: string; - public registry: (bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound) => void; + public registry: interfaces.ContainerModuleCallBack; - public constructor(registry: (bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound) => void) { + public constructor(registry: interfaces.ContainerModuleCallBack) { this.guid = guid(); this.registry = registry; } diff --git a/src/interfaces/interfaces.ts b/src/interfaces/interfaces.ts index edfc0fdcb..b32c1e9bc 100644 --- a/src/interfaces/interfaces.ts +++ b/src/interfaces/interfaces.ts @@ -164,6 +164,7 @@ namespace interfaces { parent: Container | null; options: ContainerOptions; bind(serviceIdentifier: ServiceIdentifier): BindingToSyntax; + rebind(serviceIdentifier: interfaces.ServiceIdentifier): interfaces.BindingToSyntax; unbind(serviceIdentifier: ServiceIdentifier): void; unbindAll(): void; isBound(serviceIdentifier: ServiceIdentifier): boolean; @@ -185,6 +186,10 @@ namespace interfaces { (serviceIdentifier: ServiceIdentifier): BindingToSyntax; } + export interface Rebind extends Function { + (serviceIdentifier: ServiceIdentifier): BindingToSyntax; + } + export interface Unbind extends Function { (serviceIdentifier: ServiceIdentifier): void; } @@ -195,7 +200,16 @@ namespace interfaces { export interface ContainerModule { guid: string; - registry: (bind: Bind, unbind: Unbind, isBound: IsBound) => void; + registry: ContainerModuleCallBack; + } + + export interface ContainerModuleCallBack extends Function { + ( + bind: interfaces.Bind, + unbind: interfaces.Unbind, + isBound: interfaces.IsBound, + rebind: interfaces.Rebind + ): void; } export interface ContainerSnapshot { diff --git a/test/container/container.test.ts b/test/container/container.test.ts index 28c64e2e0..a5f3a1565 100644 --- a/test/container/container.test.ts +++ b/test/container/container.test.ts @@ -606,4 +606,25 @@ describe("Container", () => { }); + it("Should be able to override a binding using rebind", () => { + + let TYPES = { + someType: "someType" + }; + + let container = new Container(); + container.bind(TYPES.someType).toConstantValue(1); + container.bind(TYPES.someType).toConstantValue(2); + + let values1 = container.getAll(TYPES.someType); + expect(values1[0]).to.eq(1); + expect(values1[1]).to.eq(2); + + container.rebind(TYPES.someType).toConstantValue(3); + let values2 = container.getAll(TYPES.someType); + expect(values2[0]).to.eq(3); + expect(values2[1]).to.eq(undefined); + + }); + }); diff --git a/test/container/container_module.test.ts b/test/container/container_module.test.ts index 0dc36fdd4..50568aecb 100644 --- a/test/container/container_module.test.ts +++ b/test/container/container_module.test.ts @@ -51,4 +51,46 @@ describe("ContainerModule", () => { }); + it("Should be able to override a binding using rebind within a container module", () => { + + let TYPES = { + someType: "someType" + }; + + let container = new Container(); + + let module1 = new ContainerModule( + ( + bind: interfaces.Bind, + unbind: interfaces.Unbind, + isBound: interfaces.IsBound + ) => { + bind(TYPES.someType).toConstantValue(1); + bind(TYPES.someType).toConstantValue(2); + } + ); + + let module2 = new ContainerModule( + ( + bind: interfaces.Bind, + unbind: interfaces.Unbind, + isBound: interfaces.IsBound, + rebind: interfaces.Rebind + ) => { + rebind(TYPES.someType).toConstantValue(3); + } + ); + + container.load(module1); + let values1 = container.getAll(TYPES.someType); + expect(values1[0]).to.eq(1); + expect(values1[1]).to.eq(2); + + container.load(module2); + let values2 = container.getAll(TYPES.someType); + expect(values2[0]).to.eq(3); + expect(values2[1]).to.eq(undefined); + + }); + }); diff --git a/test/inversify.test.ts b/test/inversify.test.ts index 2b2afc884..133f2c48e 100644 --- a/test/inversify.test.ts +++ b/test/inversify.test.ts @@ -4,7 +4,6 @@ import { interfaces } from "../src/interfaces/interfaces"; import { expect } from "chai"; import "es6-symbol/implement"; import * as ERROR_MSGS from "../src/constants/error_msgs"; -import * as Stubs from "./utils/stubs"; import { Container, injectable, inject, multiInject, tagged, named, targetName, decorate, typeConstraint, @@ -2658,49 +2657,6 @@ describe("InversifyJS", () => { }); - it("Should display a error when injecting into an abstract class", () => { - - @injectable() - class Soldier extends Stubs.BaseSoldier { } - - @injectable() - class Archer extends Stubs.BaseSoldier { } - - @injectable() - class Knight extends Stubs.BaseSoldier { } - - @injectable() - class Sword implements Stubs.Weapon { } - - @injectable() - class Bow implements Stubs.Weapon { } - - @injectable() - class DefaultWeapon implements Stubs.Weapon { } - - let container = new Container(); - - container.bind("Weapon").to(DefaultWeapon).whenInjectedInto(Soldier); - container.bind("Weapon").to(Sword).whenInjectedInto(Knight); - container.bind("Weapon").to(Bow).whenInjectedInto(Archer); - container.bind("BaseSoldier").to(Soldier).whenTargetNamed("default"); - container.bind("BaseSoldier").to(Knight).whenTargetNamed("knight"); - container.bind("BaseSoldier").to(Archer).whenTargetNamed("archer"); - - let throw1 = () => { container.getNamed("BaseSoldier", "default"); }; - let throw2 = () => { container.getNamed("BaseSoldier", "knight"); }; - let throw3 = () => { container.getNamed("BaseSoldier", "archer"); }; - - function getError(className: string) { - return ERROR_MSGS.ARGUMENTS_LENGTH_MISMATCH_1 + className + ERROR_MSGS.ARGUMENTS_LENGTH_MISMATCH_2; - } - - expect(throw1).to.throw(getError("Soldier")); - expect(throw2).to.throw(getError("Knight")); - expect(throw3).to.throw(getError("Archer")); - - }); - it("Should be able to inject a regular derived class", () => { const SYMBOLS = { diff --git a/test/node/error_messages.test.ts b/test/node/error_messages.test.ts index e6808fc0f..534544c0b 100644 --- a/test/node/error_messages.test.ts +++ b/test/node/error_messages.test.ts @@ -1,5 +1,6 @@ import { expect } from "chai"; import * as ERROR_MSGS from "../../src/constants/error_msgs"; +import * as Stubs from "../utils/stubs"; import { Container, injectable } from "../../src/inversify"; @@ -83,4 +84,47 @@ describe("Error message when resolving fails", () => { }); + it("Should display a error when injecting into an abstract class", () => { + + @injectable() + class Soldier extends Stubs.BaseSoldier { } + + @injectable() + class Archer extends Stubs.BaseSoldier { } + + @injectable() + class Knight extends Stubs.BaseSoldier { } + + @injectable() + class Sword implements Stubs.Weapon { } + + @injectable() + class Bow implements Stubs.Weapon { } + + @injectable() + class DefaultWeapon implements Stubs.Weapon { } + + let container = new Container(); + + container.bind("Weapon").to(DefaultWeapon).whenInjectedInto(Soldier); + container.bind("Weapon").to(Sword).whenInjectedInto(Knight); + container.bind("Weapon").to(Bow).whenInjectedInto(Archer); + container.bind("BaseSoldier").to(Soldier).whenTargetNamed("default"); + container.bind("BaseSoldier").to(Knight).whenTargetNamed("knight"); + container.bind("BaseSoldier").to(Archer).whenTargetNamed("archer"); + + let throw1 = () => { container.getNamed("BaseSoldier", "default"); }; + let throw2 = () => { container.getNamed("BaseSoldier", "knight"); }; + let throw3 = () => { container.getNamed("BaseSoldier", "archer"); }; + + function getError(className: string) { + return ERROR_MSGS.ARGUMENTS_LENGTH_MISMATCH_1 + className + ERROR_MSGS.ARGUMENTS_LENGTH_MISMATCH_2; + } + + expect(throw1).to.throw(getError("Soldier")); + expect(throw2).to.throw(getError("Knight")); + expect(throw3).to.throw(getError("Archer")); + + }); + }); diff --git a/wiki/container_api.md b/wiki/container_api.md index b274614f9..4b1c76095 100644 --- a/wiki/container_api.md +++ b/wiki/container_api.md @@ -1,9 +1,10 @@ # The Container API -The InversifyJS container provides some helpers to resolve multi-injections +The InversifyJS container provides some helpers to resolve multi-injections and ambiguous bindings. ## Container Options + The default scope is `transient` and you can change the scope of a type when declaring a binding: ```ts @@ -17,7 +18,8 @@ You can use container options to change the default scope used at application le let container = new Container({ defaultScope: "Singleton" }); ``` -## Container.merge(a: Container, b: Container); +## Container.merge(a: Container, b: Container) + Merges two containers into one: ```ts @@ -67,6 +69,7 @@ expect(gameContainer.get(JAPAN_EXPANSION_TYPES.Katana).name).to.eql("Kat ``` ## container.getNamed() + Named bindings: ```ts @@ -79,6 +82,7 @@ let shuriken = container.getNamed("Weapon", "chinese"); ``` ## container.getTagged() + Tagged bindings: ```ts @@ -91,6 +95,7 @@ let shuriken = container.getTagged("Weapon", "faction", "ninja"); ``` ## container.getAll() + Get all available bindings for a given identifier: ```ts @@ -102,6 +107,7 @@ let weapons = container.getAll("Weapon"); // returns Weapon[] ``` ## container.getAllNamed() + Get all available bindings for a given identifier that match the given named constraint: @@ -132,6 +138,7 @@ expect(es[1].goodbye).to.eql("adios"); ## container.getAllTagged() + Get all available bindings for a given identifier that match the given named constraint: @@ -160,7 +167,8 @@ expect(es[0].hello).to.eql("hola"); expect(es[1].goodbye).to.eql("adios"); ``` -## container.isBound() +## container.isBound(serviceIdentifier: ServiceIdentifier) + You can use the `isBound` method to check if there are registered bindings for a given service identifier. ```ts @@ -192,10 +200,11 @@ container.isBound(katanaSymbol)).eql(false); ``` ## container.isBoundNamed(serviceIdentifier: ServiceIdentifier, named: string) + You can use the `isBoundNamed` method to check if there are registered bindings for a given service identifier with a given named constraint. > NOTE: We can only identify basic tagged bindings not complex constraints (e.g ancerstors). -> Users can try-catch calls to container.get("T") if they really need to do check if a +> Users can try-catch calls to container.get("T") if they really need to do check if a > binding with a complex constraint is available. ```ts @@ -220,10 +229,11 @@ expect(container.isBoundNamed(zero, validDivisor)).to.eql(true); ``` ## container.isBoundTagged(serviceIdentifier: ServiceIdentifier, key: string, value: any) + You can use the `isBoundTagged` method to check if there are registered bindings for a given service identifier with a given tagged constraint. > NOTE: We can only identify basic tagged bindings not complex constraints (e.g ancerstors). -> Users can try-catch calls to container.get("T") if they really need to do check if a +> Users can try-catch calls to container.get("T") if they really need to do check if a > binding with a complex constraint is available. ```ts @@ -245,3 +255,27 @@ container.bind(zero).toConstantValue(1).whenTargetTagged(isValidDivisor, expect(container.isBoundTagged(zero, isValidDivisor, false)).to.eql(true); expect(container.isBoundTagged(zero, isValidDivisor, true)).to.eql(true); ``` + +## container.rebind(serviceIdentifier: ServiceIdentifier) + +You can use the `rebind` method to replace all the existing bindings for a given `serviceIdentifier`. +The function returns an instance of `BindingToSyntax` which allows to create the replacement binding. + +```ts +let TYPES = { + someType: "someType" +}; + +let container = new Container(); +container.bind(TYPES.someType).toConstantValue(1); +container.bind(TYPES.someType).toConstantValue(2); + +let values1 = container.getAll(TYPES.someType); +expect(values1[0]).to.eq(1); +expect(values1[1]).to.eq(2); + +container.rebind(TYPES.someType).toConstantValue(3); +let values2 = container.getAll(TYPES.someType); +expect(values2[0]).to.eq(3); +expect(values2[1]).to.eq(undefined); +``` \ No newline at end of file diff --git a/wiki/container_modules.md b/wiki/container_modules.md index f3329b83e..02f4179ad 100644 --- a/wiki/container_modules.md +++ b/wiki/container_modules.md @@ -1,4 +1,5 @@ # Declaring container modules + Container modules can help you to manage the complexity of your bindings in very large applications. ```ts @@ -6,10 +7,17 @@ let warriors = new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un bind("Ninja").to(Ninja); }); -let weapons = new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind) => { - bind("Katana").to(Katana); - bind("Shuriken").to(Shuriken); -}); +let weapons = new ContainerModule( + ( + bind: interfaces.Bind, + unbind: interfaces.Unbind, + isBound: interfaces.IsBound, + rebind: interfaces.Rebind + ) => { + bind("Katana").to(Katana); + bind("Shuriken").to(Shuriken); + } +); let container = new Container(); container.load(warriors, weapons);