Decorators
This section covers Inversify decorators used to provide class metadata.
injectable
Decorator used to set class metadata so containers can receive class-emitted metadata.
It's highly recommended to annotate every class provided as a service with the @injectable
decorator. However, it's not mandatory in every single case.
When is injectable mandatory?
Whenever class-emitted metadata is expected.
Consider the following sample code:
import 'reflect-metadata';
import { injectable } from 'inversify';
@injectable()
class B {
readonly foo: string = 'foo';
}
@injectable()
class A {
constructor(public readonly b: B) {}
}
A CommonJS transpilation with the emitDecoratorMetadata option enabled might look like this:
Object.defineProperty(exports, "__esModule", { value: true });
require("reflect-metadata");
const inversify_1 = require("inversify");
let B = class B {
foo = 'foo';
};
B = __decorate([
(0, inversify_1.injectable)()
], B);
let A = class A {
b;
constructor(b) {
this.b = b;
}
};
A = __decorate([
(0, inversify_1.injectable)(),
__metadata("design:paramtypes", [B])
], A);
TypeScript emits class metadata if and only if there are any class decorators applied to the target class. If we remove the @injectable
from A
, the transpiled code looks very different:
Object.defineProperty(exports, "__esModule", { value: true });
require("reflect-metadata");
class B {
foo = 'foo';
}
class A {
b;
constructor(b) {
this.b = b;
}
}
This time, no class metadata is emitted even if the emitDecoratorMetadata
TypeScript option is enabled, causing trouble at execution time.
inject
Decorator used to establish a relation between a constructor argument or a class property and a service ID.
When resolving an instance of the class, the target constructor argument or property will be resolved in the same way container.get behaves.
Example: decorating a class constructor argument
@injectable()
class Ninja {
constructor(
@inject(weaponServiceId)
public readonly weapon: Weapon,
) {}
}
const container: Container = new Container();
container.bind(Ninja).toSelf();
container.bind(weaponServiceId).to(Katana);
const ninja: Ninja = container.get(Ninja);
// Returns 10
const ninjaWeaponDamage: number = ninja.weapon.damage;
Example: decorating a property
@injectable()
class Ninja {
@inject(weaponServiceId)
public readonly weapon!: Weapon;
}
const container: Container = new Container();
container.bind(Ninja).toSelf();
container.bind(weaponServiceId).to(Katana);
const ninja: Ninja = container.get(Ninja);
// Returns 10
const ninjaWeaponDamage: number = ninja.weapon.damage;
multiInject
Decorator used to establish a relation between a constructor argument or a class property and a service ID.
When resolving an instance of the class, the target constructor argument or property will be resolved in the same way container.getAll behaves with the enforceBindingConstraints
flag enabled.
Example: decorating a property
@injectable()
class Ninja {
@multiInject(weaponServiceId)
public readonly weapon!: Weapon[];
}
const container: Container = new Container();
container.bind(Ninja).toSelf();
container.bind(weaponServiceId).to(Katana);
const ninja: Ninja = container.get(Ninja);
// Returns 10
const ninjaWeaponDamage: number | undefined = ninja.weapon[0]?.damage;
named
Decorator used to establish a relation between a constructor argument or a class property and a metadata name.
@injectable()
class Ninja {
public katana: Weapon;
public shuriken: Weapon;
constructor(
@inject('Weapon') @named('melee') katana: Weapon,
@inject('Weapon') @named('ranged') shuriken: Weapon,
) {
this.katana = katana;
this.shuriken = shuriken;
}
}
const container: Container = new Container();
container.bind<Weapon>('Weapon').to(Katana).whenTargetNamed('melee');
container.bind<Weapon>('Weapon').to(Shuriken).whenTargetNamed('ranged');
container.bind(Ninja).toSelf();
const ninja: Ninja = container.get(Ninja);
optional
Decorator used to establish that a target constructor argument or property is optional and, therefore, it shall not be resolved if no bindings are found for the associated service ID.
@injectable()
class Ninja {
public katana: Weapon;
public shuriken: Weapon | undefined;
constructor(
@inject('Katana') katana: Weapon,
@inject('Shuriken') @optional() shuriken: Weapon | undefined,
) {
this.katana = katana;
this.shuriken = shuriken;
}
}
const container: Container = new Container();
container.bind<Weapon>('Katana').to(Katana);
container.bind(Ninja).toSelf();
// Returns a "Ninja" instance with a "Katana" katana property and undefined shuriken property.
const ninja: Ninja = container.get(Ninja);
postConstruct
Decorator used to establish an activation handler for the target class. Refer to the docs for more information.
interface Weapon {
damage: number;
}
export class Katana implements Weapon {
#damage: number = 10;
public get damage(): number {
return this.#damage;
}
@postConstruct()
public improve(): void {
this.#damage += 2;
}
}
const container: Container = new Container();
container.bind<Weapon>('Weapon').to(Katana);
// Katana.damage is 12
const katana: Weapon = container.get<Weapon>('Weapon');
preDestroy
Decorator used to establish a deactivation handler for the target class. Refer to the docs for more information.
interface Weapon {
damage: number;
}
export class Katana implements Weapon {
readonly #damage: number = 10;
public get damage(): number {
return this.#damage;
}
@preDestroy()
public onDeactivation(): void {
console.log(`Deactivating weapon with damage ${this.damage.toString()}`);
}
}
const container: Container = new Container();
container.bind<Weapon>('Weapon').to(Katana).inSingletonScope();
container.get('Weapon');
container.unbind('Weapon');
tagged
Decorator used to establish a relation between a constructor argument or a class property and a metadata tag.
@injectable()
class Ninja {
public katana: Weapon;
public shuriken: Weapon;
constructor(
@inject('Weapon') @tagged('weaponKind', 'melee') katana: Weapon,
@inject('Weapon') @tagged('weaponKind', 'ranged') shuriken: Weapon,
) {
this.katana = katana;
this.shuriken = shuriken;
}
}
const container: Container = new Container();
container
.bind<Weapon>('Weapon')
.to(Katana)
.whenTargetTagged('weaponKind', 'melee');
container
.bind<Weapon>('Weapon')
.to(Shuriken)
.whenTargetTagged('weaponKind', 'ranged');
container.bind(Ninja).toSelf();
const ninja: Ninja = container.get(Ninja);
targetName
Decorator used to establish a relation between a constructor argument or a class property name at design time.
Bundlers might minify code, altering class property names. This decorator keeps track of the original name.
This property is kept in the name
property of the target
request in a binding constraint.
@injectable()
class Ninja implements Ninja {
public katana: Weapon;
public shuriken: Weapon;
constructor(
@inject("Weapon") @targetName("katana") katana: Weapon,
@inject("Weapon") @targetName("shuriken") shuriken: Weapon
) {
this.katana = katana;
this.shuriken = shuriken;
}
}
container.bind<Weapon>("Weapon").to(Katana).when((request: interfaces.Request) => {
return request.target.name.equals("katana");
});
unmanaged
Decorator used to establish that Inversify should not inject the target constructor argument or property whatsoever.
import { Container, injectable, unmanaged } from 'inversify';
@injectable()
class Base {
public prop: string;
constructor(@unmanaged() arg: string) {
this.prop = arg;
}
}
@injectable()
class Derived extends Base {
constructor() {
super('inherited-value');
}
}
const container: Container = new Container();
const derived: Derived = container.resolve(Derived);
// Returns 'inherited-value'
const derivedProp: string = derived.prop;