Class Container<Services>

Represents the dependency injection container that manages the registration, creation, and retrieval of services. The Container class is central to the dependency injection process, facilitating typesafe injection and retrieval of services based on tokens.

const fooFactory = Injectable('Foo', () => new Foo())
const barFactory = Injectable('Bar', ['Foo'] as const, (foo: Foo) => new Bar(foo))
const container = Container.provides(fooFactory).provides(barFactory)

const bar = container.get('Bar')

Type Parameters

  • Services = {}

Constructors

Properties

factories: Readonly<Factories<Services>>

Methods

  • Appends a new service instance to an existing array within the container using an InjectableFunction.

    Type Parameters

    • Token extends string | number | symbol
    • Tokens extends readonly ValidTokens<Services>[]
    • Service extends unknown

    Parameters

    Returns Container<Services>

    The updated Container, now including the new service instance appended to the array specified by the token.

    // Assume there's a container with an array ready to hold service instances
    const container = Container.fromObject({ services: [] as Service[] });
    // Append a new Service instance to the 'services' array using a factory function
    const newContainer = container.append(Injectable('services', () => new Service()));
    // Retrieve the services array to see the added Service instance
    console.log(newContainer.get('services').length); // prints 1;
  • Appends an injectable class factory to the array associated with a specified token in the current Container, then returns the new Container with the updated value. This method is applicable under the following conditions:

    1. The Container already contains an array associated with the given token.
    2. The type of the items in the array matches the type of the value being appended.
    const container = Container.fromObject({ services: [] as Service[] });
    const newContainer = container.appendClass('services', Service);
    console.log(newContainer.get('services').length); // prints 1;

    @param token - A unique Token which will correspond to the previously defined typed array.
    @param cls - A class with a constructor that takes dependencies as arguments, which returns the Service.
    @returns The updated Container with the new service instance appended to the specified array

    Type Parameters

    • Token extends string | number | symbol
    • Tokens extends readonly ValidTokens<Services>[]
    • Service extends unknown

    Parameters

    Returns Container<Services>

  • Appends a value to the array associated with a specified token in the current Container, then returns the new Container with the updated value. This method is applicable under the following conditions:

    1. The Container already contains an array associated with the given token.
    2. The type of the items in the array matches the type of the value being appended.
    const container = Container.fromObject({ services: [1, 2, 3] as number[] });
    const newContainer = container.appendValue('services', 4);
    console.log(newContainer.get('services')); // prints [1, 2, 3, 4];

    Type Parameters

    • Token extends string | number | symbol
    • Service extends unknown

    Parameters

    • token: Token

      A unique Token which will correspond to the previously defined typed array.

    • value: Service

      A value to append to the array.

    Returns Container<Services>

    The updated Container with the appended value in the specified array.

  • Creates a copy of this Container, optionally scoping specified services to the new copy. Unspecified services are shared between the original and copied containers, while factory functions for scoped services are re-invoked upon service resolution in the new container.

    This can be useful, for example, if different parts of an application wish to use the same Service interface, but do not want to share a reference to same Service instance.

    Consider an example where we have a UserListService that manages a list of users. If our application needs to display two user lists that can be edited independently (e.g., in separate components or pages), it would be beneficial to create a distinct Container for each list component. By scoping the UserListService to each Container, we ensure that each component receives its own independent copy of the service. This setup allows for independent edits to each user list without any overlap or interference between the two components.

    Type Parameters

    • Tokens extends readonly (keyof Services)[]

    Parameters

    • OptionalscopedServices: Tokens

      An optional list of tokens for Services to be scoped to the new Container copy. Services not specified will be shared with the original Container, while specified ones will be re-instantiated in the new Container.

    Returns Container<Services>

    A new Container copy that shares the original's services, with specified services scoped as unique instances to the new Container.

    // Create the original container and provide the UserListService
    const originalContainer = Container.provides(Injectable('UserListService', () => new UserListService()));

    // Create a new Container copy with UserListService scoped, allowing for independent user lists
    const newListContainer = originalContainer.copy(['UserListService']);

    // Each Container now manages its own independent UserListService service instance
  • Retrieves a reference to this Container.

    Parameters

    Returns this

    This Container.

  • Retrieves a Service from the Container by its token. On first request, the service's factory function is invoked and the result is memoized for future requests, ensuring singleton behavior.

    Type Parameters

    • Token extends string | number | symbol

    Parameters

    • token: Token

      A unique token corresponding to a Service

    Returns Services[Token]

    A Service corresponding to the given Token.

  • Merges additional services from a given PartialContainer into this container, creating a new Container instance. Services defined in the PartialContainer take precedence in the event of token conflicts, meaning any service in the PartialContainer with the same token as one in this container will override the existing service.

    If the same PartialContainer is provided to multiple containers, each resulting container will have its own independent instance of the services defined in the PartialContainer, ensuring no shared state between them.

    Type Parameters

    • AdditionalServices
    • Dependencies
    • FulfilledDependencies

    Parameters

    Returns Container<AddServices<Services, AdditionalServices>>

    A new Container instance that combines the services of this container with those from the provided PartialContainer, with services from the PartialContainer taking precedence in case of conflicts.

  • Merges services from another Container into this container, creating a new Container instance. Services from the provided Container take precedence in the event of token conflicts.

    Importantly, services from the provided Container are shared between the original (source) container and the new (destination) container created by this method. This means that both containers will reference the same service instances, ensuring consistency but not isolation.

    If isolation is required (i.e., separate instances of the services in different containers), the source container should be copied before being passed to this method. This ensures that new instances of the services are created in the new container, avoiding shared state issues.

    Type Parameters

    • AdditionalServices

    Parameters

    Returns Container<AddServices<Services, AdditionalServices>>

    A new Container instance that combines services from this container with those from the provided container, with services from the provided container taking precedence in case of conflicts.

  • Registers a new service in this Container using an InjectableFunction. This function defines how the service is created, including its dependencies and the token under which it will be registered. When called, this method adds the service to the container, ready to be retrieved via its token.

    The InjectableFunction must specify:

    • A unique Token identifying the service.
    • A list of Tokens representing the dependencies needed to create the service.

    This method ensures type safety by verifying that all required dependencies are available in the container and match the expected types. If a dependency is missing or a type mismatch occurs, a compiler error is raised, preventing runtime errors and ensuring reliable service creation.

    Type Parameters

    • Token extends TokenType
    • Tokens extends readonly ValidTokens<Services>[]
    • Service

    Parameters

    Returns Container<AddService<Services, Token, Service>>

    A new Container instance containing the added service, allowing chaining of multiple provides calls.

  • Registers a service in the container using a class constructor, simplifying the service creation process.

    This method is particularly useful when the service creation logic can be encapsulated within a class constructor.

    Type Parameters

    • Token extends TokenType
    • Service
    • Tokens extends readonly ValidTokens<Services>[]

    Parameters

    • token: Token

      A unique Token used to identify and retrieve the service from the container.

    • cls: InjectableClass<Services, Service, Tokens>

      A class with a constructor that takes dependencies as arguments and a static dependencies field specifying these dependencies.

    Returns Container<AddService<Services, Token, Service>>

    A new Container instance containing the newly created service, allowing for method chaining.

  • Registers a static value as a service in the container. This method is ideal for services that do not require dynamic instantiation and can be provided directly as they are.

    Type Parameters

    • Token extends TokenType
    • Service

    Parameters

    • token: Token

      A unique Token used to identify and retrieve the service from the container.

    • value: Service

      The actual value to register as a service. This could be anything from a simple data object, a configuration, or a pre-instantiated service object.

    Returns Container<AddService<Services, Token, Service>>

    A new Container instance that includes the provided service, allowing for chaining additional provides calls.

  • Runs the factory functions for all services listed in the provided PartialContainer, along with their dependencies that are registered within this container.

    This method is particularly useful for preemptively initializing services that require setup before use. It ensures that services are ready when needed without waiting for a lazy instantiation.

    Note: This method does not add new services to the container.

    Type Parameters

    • AdditionalServices
    • Dependencies
    • FulfilledDependencies

    Parameters

    Returns this

    The current container unchanged, with dependencies of the services listed in the provided PartialContainer initialized as needed.

    // Create initializers for caching and reporting setup that depend on a request service
    const initializers = new PartialContainer({})
    .provides(Injectable("initCache", ["request"], (request: Request) => fetchAndPopulateCache(request)))
    .provides(Injectable("setupReporter", ["request"], (request: Request) => setupReporter(request)));

    // Setup the main container with a request service and run the initializers
    const container = Container
    .provides(Injectable("request", () => (url: string) => fetch(url)))
    .run(initializers);

    // At this point, `initCache` and `setupReporter` have been executed using the `request` service.
    // And the `request` service itself has also been initialized within the `container`.
  • Runs the factory function for a specified service provided by InjectableFunction, along with its dependencies that are registered within this container.

    This method is particularly useful for services that need to be set up before they are used. It ensures that the service is ready when needed, without relying on lazy instantiation.

    Note: This method does not add new services to the container.

    Type Parameters

    • Token extends TokenType
    • Tokens extends readonly ValidTokens<Services>[]
    • Service

    Parameters

    Returns this

    The current container unchanged, with dependencies of the provided InjectableFunction initialized as needed.

    // Setup a container with a request service and directly run the `initCache` service
    const container = Container
    .provides(Injectable("request", () => (url: string) => fetch(url)))
    .run(Injectable("initCache", ["request"], (request: Request) => fetchAndPopulateCache(request)));

    // At this point, `initCache` has been executed using the `request` service.
    // And the `request` service itself has also been initialized.
  • Creates a new Container from a plain object containing service definitions. Each property of the object is treated as a unique token, and its corresponding value is registered in the Container as a service under that token. This method offers a convenient way to quickly bootstrap a container with predefined services.

    Type Parameters

    • Services extends {
          [s: string]: any;
      }

    Parameters

    • services: Services

      A plain object where each property (token) maps to a service value. This object defines the initial set of services to be contained within the new Container instance.

    Returns Container<Services>

    A new Container instance populated with the provided services.

    // Creating a container with simple value services
    const container = Container.fromObject({ foo: 1, bar: 'baz' });

    // Retrieving services from the container
    console.log(container.get('foo')); // prints 1
    console.log(container.get('bar')); // prints 'baz'

    In this example, container is of type Container<{ foo: number, bar: string }> indicating that it holds services under the tokens 'foo' and 'bar' with corresponding types.

  • Creates a new [Container] by providing a [PartialContainer] that has no dependencies.

    Type Parameters

    • Services

    Parameters

    Returns Container<Services>

    // Register a single service
    const container = Container.provides(Injectable('Logger', () => new Logger()));

    // Extend container with another container or partial container
    const extendedContainer = Container.provide(existingContainer);
  • Creates a new [Container] by providing a Service that has no dependencies.

    Type Parameters

    • Token extends TokenType
    • Service

    Parameters

    • fn: {
          dependencies: [];
          token: Token;
          (...args: []): Service;
      }

    Returns Container<{
        [K in TokenType]: K extends never
            ? K<K> extends Token
                ? Service
                : {}[K<K>]
            : Service
    }>

    // Register a single service with no dependencies
    const container = Container.provides(Injectable('Logger', () => new Logger()));
  • Registers a static value as a service in a new Container. Ideal for services that don't require instantiation or dependencies.

    NOTE: This method acts as a syntactic shortcut, essentially registering a factory function that directly returns the provided value.

    Type Parameters

    • Token extends TokenType
    • Service

    Parameters

    • token: Token

      A unique Token identifying the service within the container. This token is used to retrieve the value.

    • value: Service

      The value or instance to register as a service within the container. This can be of any type.

    Returns Container<{
        [K in TokenType]: K extends never
            ? K<K> extends Token
                ? Service
                : {}[K<K>]
            : Service
    }>

    A new Container instance with the specified service registered.

    // Registering an instance of a class
    const logger = new Logger();
    const container = Container.providesValue('Logger', logger);

    // This is effectively a shortcut for the following, where an Injectable is explicitly created
    const container2 = Container.provides(Injectable('Logger', () => logger);