Skip to main content
Version: 7.x

DI Hierarchy

InversifyJS is a popular library for implementing inversion of control (IoC) and dependency injection (DI) in TypeScript applications. It supports hierarchical dependency injection, which can be a powerful tool in complex applications.

With InversifyJS's hierarchical injection system, you can create a hierarchy of containers where each container can have a parent container. This allows for better organization and separation of concerns in your application.

When a dependency needs to be injected, InversifyJS starts by looking in the current container for a binding. If the binding is not found, it moves up the hierarchy to the parent container and continues the search. This process continues until a binding is found or the top-level parent container is reached.

Binding overrides

Found bindings might override ancestor bindings even if their constraints are not met. For example, if a named binding is found in the child container for the requested service, that binding overrides parent bindings even if this binding is later discarded in a non-named resolution request.

DI hierarchies and cached bindings

When using hierarchical injection, be aware that cached bindings from the first resolution will be used for subsequent resolution, even if the call comes from another child container.

@injectable()
class Samurai {
constructor(
@inject(Katana)
public katana: Katana,
) {}
}

const parentContainer: Container = new Container();
parentContainer.bind(Samurai).toSelf().inSingletonScope();
parentContainer.bind(Katana).toSelf();

const childContainer: Container = new Container({ parent: parentContainer });
childContainer.bind(Katana).to(LegendaryKatana);

// The result of this resolution will be cached in the samurai binding
childContainer.get(Samurai);

// This samurai will have a LegendaryKatana injected
const samurai: Samurai = parentContainer.get(Samurai);

If this behaviour is unwanted, consider using ContainerModule instead. This way, you can load it in both containers. Different containers will have different binding and therefore different cached values.

By using InversifyJS's hierarchical injection system, you can easily manage complex dependencies and keep your code clean and modular. It provides a flexible and scalable solution for handling dependencies in your TypeScript applications.

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);

Chained Resolution Mode

InversifyJS supports two different resolution modes when working with container hierarchies: standard resolution and chained resolution.

Standard Resolution Mode

In standard resolution mode (the default behavior), InversifyJS follows a first-found approach:

  1. First, it searches for bindings in the current container
  2. If bindings are found in the current container, those bindings are used exclusively
  3. If no bindings are found in the current container, it moves up to the parent container
  4. This process continues until bindings are found or the top-level container is reached

This means that if a child container has any bindings for a service, the parent container's bindings for that same service will be ignored entirely:

const parentContainer: Container = new Container();

const container: Container = new Container({
parent: parentContainer,
});

parentContainer.bind<Weapon>('Weapon').to(Katana);
container.bind<Weapon>('Weapon').to(Shuriken);

// returns Weapon[] with only a Shuriken instance
const weapons: Weapon[] = container.getAll<Weapon>('Weapon', {
chained: false,
});

Chained Resolution Mode

Chained resolution mode allows you to collect bindings from all levels of the container hierarchy. When using getAll() or getAllAsync() with the chained: true option:

  1. Bindings are collected from the current container
  2. Then bindings are collected from the parent container
  3. This continues recursively up the entire hierarchy
  4. All collected bindings are combined and returned

This is particularly useful when you want to aggregate services from different layers of your application (e.g., core services from a parent container and feature-specific services from child containers).

const parentContainer: Container = new Container();

const container: Container = new Container({
parent: parentContainer,
});

parentContainer.bind<Weapon>('Weapon').to(Katana);
container.bind<Weapon>('Weapon').to(Shuriken);

// returns Weapon[] with both Katana and Shuriken instances
const weapons: Weapon[] = container.getAll<Weapon>('Weapon', { chained: true });