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 { 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 });
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 });
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;
injectFromBase
Decorator used to inject dependencies from a base class. Child class injections take precedence over base class injections.
Arguments
The injectFromBase
decorator accepts an optional InjectFromBaseOptions
object with the following properties:
extendConstructorArguments
(boolean, defaults to true): If true, constructor arguments from the base class will be injected.extendProperties
(boolean, defaults to true): If true, properties from the base class will be injected.
Example: decorating base class constructor arguments
type Weapon = unknown;
@injectable()
abstract class BaseSoldier {
public weapon: Weapon;
constructor(@inject('Weapon') weapon: Weapon) {
this.weapon = weapon;
}
}
@injectable()
@injectFromBase({
extendConstructorArguments: true,
extendProperties: false,
})
class Soldier extends BaseSoldier {}
// Returns a soldier with a weapon
const soldier: Soldier = container.get(Soldier);
Example: decorating base class properties
type Weapon = unknown;
@injectable()
abstract class BaseSoldier {
@inject('Weapon')
public weapon: Weapon;
}
@injectable()
@injectFromBase({
extendConstructorArguments: false,
extendProperties: true,
})
class Soldier extends BaseSoldier {}
// Returns a soldier with a weapon
const soldier: Soldier = container.get(Soldier);
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.
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).whenNamed('melee');
container.bind<Weapon>('Weapon').to(Shuriken).whenNamed('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;
}
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');
await 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).whenTagged('weaponKind', 'melee');
container
.bind<Weapon>('Weapon')
.to(Shuriken)
.whenTagged('weaponKind', 'ranged');
container.bind(Ninja).toSelf();
const ninja: Ninja = container.get(Ninja);
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();
container.bind(Derived).toSelf();
const derived: Derived = container.get(Derived);
// Returns 'inherited-value'
const derivedProp: string = derived.prop;