Migrating from v6
InversifyJS v7 introduces a few breaking changes. This guide will help you migrate your existing InversifyJS v6 code to v7.
Container API
Autobinding
In v6, you could enable autobinding by passing the autoBindInjectable
option to the container constructor. In v7, this option has been renamed to autobind
which can be passed either as part of Container
constructor options or Container.get
options.
In v6, container.resolve
would automatically bind the resolved service to the container. In v7, this behavior has been removed. You can enable this behavior by passing the autobind
option.
export class Katana {
public readonly damage: number = 10;
}
@injectable()
export class Samurai {
public readonly katana: Katana;
constructor(katana: Katana) {
this.katana = katana;
}
}
const container: Container = new Container();
const samurai: Samurai = container.get(Samurai, { autobind: true });
Custom metadata and middlewares
This is the only feature that has been removed in v7 with no direct replacement. The API was not super useful, not widely used and was one of the main reasons for the complexity of the library. We might bring it back in the future with a better API.
isBound like methods
Container.isCurrentBound
, Container.isBoundNamed
, Container.isBoundTagged
behavior can be achieved relying on Container.isBound
with isBoundOptions
.
get like methods
Container
has been updated with no getNamed
, getTagged
, tryGet
, tryGetNamed
and tryGetTagged
methods in favor of Container.get
with OptionalGetOptions
options.
The same applies to Container.getAll
, Container.getAllAsync
and Container.getAsync
, they all now receive a GetOptions
object which can be used to pass expected name or tags.
const container: Container = new Container();
container.bind<Weapon>('Weapon').to(Katana).whenNamed('Katana');
const katana: Weapon = container.get<Weapon>('Weapon', { name: 'Katana' });
load and unload methods
This methods are now asynncronous and therefore return a promise.
const warriorsModule: ContainerModule = new ContainerModule(
(options: ContainerModuleLoadOptions) => {
options.bind<Ninja>('Ninja').to(Ninja);
},
);
const weaponsModule: ContainerModule = new ContainerModule(
(options: ContainerModuleLoadOptions) => {
options.bind<Katana>('Weapon').to(Katana).whenNamed('Melee');
options.bind<Shuriken>('Weapon').to(Shuriken).whenNamed('Ranged');
},
);
await container.load(warriorsModule, weaponsModule);
const ninja: Ninja = container.get('Ninja');
unbind and unbindAll methods
Container.unbind
and Container.unbindAll
methods are now asynchronous and return a promise. Container.unbindAsync
and Container.unbindAllAsync
methods have been removed.
Parent and child containers
In v6, you could create a child container by calling the createChild
method on a parent container. In v7, this method has been removed. Instead, you can create a child container by passing the parent container to the constructor of the child container.
class Katana {}
const parentContainer: Container = new Container();
parentContainer.bind(weaponIdentifier).to(Katana);
const childContainer: Container = new Container({ parent: parentContainer });
const katana: Katana = childContainer.get(weaponIdentifier);
ContainerModule API
Container module load options have been updated to be an object.
const warriorsModule: ContainerModule = new ContainerModule(
(options: ContainerModuleLoadOptions) => {
options.bind<Ninja>('Ninja').to(Ninja);
},
);
const weaponsModule: ContainerModule = new ContainerModule(
(options: ContainerModuleLoadOptions) => {
options.bind<Katana>('Weapon').to(Katana).whenNamed('Melee');
options.bind<Shuriken>('Weapon').to(Shuriken).whenNamed('Ranged');
},
);
await container.load(warriorsModule, weaponsModule);
const ninja: Ninja = container.get('Ninja');
BindingFluentSyntax API
Use of ResolutionContext instead of interfaces.Context
In v6, the Context
class was used to pass contextual information. Too many internal data structures were exposed to the user. In v7, the ResolutionContext
class is used to pass contextual information. This class is more focused on the user's needs and hides internal data structures to keep the API simple and maintainable.
@injectable()
class Katana {
public use(): string {
return 'hit!';
}
}
container
.bind<Katana>('Katana')
.to(Katana)
.onActivation((_context: ResolutionContext, katana: Katana) => {
const handler: ProxyHandler<() => string> = {
apply: function (
target: () => string,
thisArgument: unknown,
argumentsList: [],
) {
console.log(`Starting: ${new Date().getTime().toString()}`);
const result: string = target.apply(thisArgument, argumentsList);
console.log(`Finished: ${new Date().getTime().toString()}`);
return result;
},
};
katana.use = new Proxy(katana.use.bind(katana), handler);
return katana;
});
Use of BindingMetadata instead of interfaces.Request
In v6, binding constraints received a Request
object. In v7, binding constraints receive a BindingMetadata
object. This object is more focused on the user's needs and hides internal data structures to keep the API simple and maintainable.
const ninjaId: symbol = Symbol.for('Ninja');
const weaponId: symbol = Symbol.for('Weapon');
@injectable()
class Ninja {
constructor(
@inject(weaponId)
@named('shuriken')
public readonly weapon: Weapon,
) {}
}
container.bind<Ninja>(ninjaId).to(Ninja);
const whenTargetNamedConstraint: (
name: string,
) => (bindingMetadata: BindingMetadata) => boolean =
(name: string) =>
(bindingMetadata: BindingMetadata): boolean =>
bindingMetadata.name === name;
container
.bind<Weapon>(weaponId)
.to(Katana)
.when(whenTargetNamedConstraint('katana'));
container
.bind<Weapon>(weaponId)
.to(Shuriken)
.when(whenTargetNamedConstraint('shuriken'));
const ninja: Ninja = container.get(ninjaId);
// Returns 5
const ninjaDamage: number = ninja.weapon.damage;