yarn add inversify reflect-metadata
Inversify requires TypeScript >= 4.4
and the experimentalDecorators
, emitDecoratorMetadata
, types
and lib
compilation options in your tsconfig.json
file
JS supporting OOP with class-based inheritance can lead to dangerous outcomes (more in oop section).
Inversify tackles this problem by:
Start by creating interfaces (abstractions)
// interfaces.ts export interface Warrior { fight(): string; sneak(): string; } export interface Weapon { hit(): string; } export interface ThrowableWeapon { throw(): string; }
Inversify needs to use the type as identifiers at runtime
Use symbols as identifiers but you can also use classes and/or string literals
You must place type declarations in a separate file
// types.ts const TYPES = { Warrior: Symbol.for("Warrior"), Weapon: Symbol.for("Weapon"), ThrowableWeapon: Symbol.for("ThrowableWeapon") }; export { TYPES };
@injectable
and @inject
decorators@injectable
Declare concretion (classes)
@injectable
decorator@inject
When a class has a dependency on an interface we also need to use the @inject
decorator to define an identifier for the interface that will be available at runtime
Symbol.for("Weapon")
and Symbol.for("ThrowableWeapon")
as runtime identifiersIn very large applications using strings as the identifiers of the types to be injected by the InversifyJS can lead to naming collisions
When we use Symbols.for()
then we are creating a new symbol if it does not exist or returning an existing symbol if it does; further guaranteeing that the uniqueness is enforced and references point to the same existing
Constructor()
Constructor takes a symbol which Inverisify provides the implementation
Constructor is a preferred method over plain property injection
// file entities.ts import { injectable, inject } from "inversify"; import "reflect-metadata"; import { Weapon, ThrowableWeapon, Warrior } from "./interfaces"; import { TYPES } from "./types"; @injectable() class Katana implements Weapon { public hit() { return "cut!"; } } @injectable() class Shuriken implements ThrowableWeapon { public throw() { return "hit!"; } } @injectable() class Ninja implements Warrior { private _katana: Weapon; private _shuriken: ThrowableWeapon; public constructor( @inject(TYPES.Weapon) katana: Weapon, @inject(TYPES.ThrowableWeapon) shuriken: ThrowableWeapon ) { // `this` keyword refers to the instance of the Ninja class // do not use function expression when defining these because it can mess up the scope/reference of `this` keyword this._katana = katana; this._shuriken = shuriken; } public fight() { return this._katana.hit(); } public sneak() { return this._shuriken.throw(); } } export { Ninja, Katana, Shuriken };
@injectable() class Ninja implements Warrior { @inject(TYPES.Weapon) private _katana: Weapon; @inject(TYPES.ThrowableWeapon) private _shuriken: ThrowableWeapon; public fight() { return this._katana.hit(); } public sneak() { return this._shuriken.throw(); } }
Create a file named inversify.config.ts
binding is service being tied to the application container
// file inversify.config.ts import { Container } from "inversify"; import { TYPES } from "./types"; import { Warrior, Weapon, ThrowableWeapon } from "./interfaces"; import { Ninja, Katana, Shuriken } from "./entities"; const myContainer = new Container(); myContainer.bind<Warrior>(TYPES.Warrior).to(Ninja); myContainer.bind<Weapon>(TYPES.Weapon).to(Katana); myContainer.bind<ThrowableWeapon>(TYPES.ThrowableWeapon).to(Shuriken); export { myContainer };
Use get<T>
method from the Container
class to resolve a dependency
import { myContainer } from "./inversify.config"; import { TYPES } from "./types"; import { Warrior } from "./interfaces"; const ninja = myContainer.get<Warrior>(TYPES.Warrior); expect(ninja.fight()).eql("cut!"); // true expect(ninja.sneak()).eql("hit!"); // true
Katana
and Shuriken
were successfully resolved and injected into Ninja