Advanced

Optional Injection

By default, if the dependency cannot be found, an exception is thrown. You can use @Optional to make the injection optional:

import { Provide, Optional } from 'dainty-di';

@Provide()
class Foo {}

@Provide()
class Bar {
  constructor(@Optional() private foo?: Foo) {}
}

Multiple Injection

If there are multiple providers for the same identifier, an exception is thrown on injection because the DI system cannot determine which unique dependency needs to be resolved. Use @MultiInject when you explicitly need to inject multiple dependencies:

import { Provide, MultiInject } from 'dainty-di';

const IFoo = Symbol('IFoo');
interface IFoo {}

@Provide({ id: IFoo })
class Foo implements IFoo {}

@Provide({ id: IFoo })
class AnotherFoo implements IFoo {}

@Provide()
class Bar {
  constructor(@MultiInject(IFoo) private foos: IFoo[]) {}
}

Child Container

You can create a child container from a parent container. The child container will inherit all the bindings from the parent container.

When looking up a dependency from the specified container, if the dependency with the specified identifier cannot be found in the current container, it is recursively looked up in the parent container. If the dependency cannot be found, an exception is thrown.

import { ContainerAccessor, DIUtility } from 'dainty-di';

const rootContainer = ContainerAccessor.getRootContainer();
const childContainer = rootContainer.createChild();

DIUtility.provideValue(
  {
    id: Symbol.for('answer'),
    value: 42,
  },
  childContainer,
);

DIUtility.getDependency(Symbol.for('answer'), childContainer); // 42

Deferred Providers

In some cases, you may want the dependency provider to defer being bound to the container. A common usage scenario is request scopes for web frameworks.

These scopes exist only for the lifetime of an HTTP request, all dependencies are resolved and instantiated at the beginning of the request, and they tend to be related to specific information on a single request, such as the request context (often referred to ctx).

To solve this, you can use the Deferred or DeferredTransient scope. With such scope, the dependency provider will only be bound until a deferred container (that is, the request container in this example) is created.

Here is a simplified example with Koa:

import { Provide, ProviderScope, Inject, DeferredScopeUtility, DIUtility } from 'dainty-di';

@Provide({ scope: ProviderScope.Deferred })
class Foo {
  private url: string;

  constructor(@Inject('ctx') ctx: any) {
    this.url = ctx.url;
  }
}

// At your request handler middleware
app.use((ctx, next) => {
  // Create a request scope container for each request
  // All deferred providers will be bound to this container automatically
  const requestContainer = DeferredScopeUtility.createDeferredScopeContainer();
  // We need to bind the current request context object to the container
  // so that the `Foo` can access the request context
  DIUtility.provideValue({
    id: 'ctx',
    value: ctx,
  });

  // Get the instance of the `Foo` with the current request context
  const foo = DIUtility.getDependency(Foo, requestContainer);
});

Custom Provider Factory

Sometimes you may want to wrap @Provide decorator of your own (e.g. @Service). Dainty DI provides you with utilities to create a decorator factory with default options:

import { DependencyIdentifier, DecoratorUtility, ProviderScope } from 'dainty-di';

const Service = (
  options: {
    id?: DependencyIdentifier;
    condition?: boolean;
    override?: boolean;
  } = {},
) => {
  return DecoratorUtility.createProvideDecoratorFactory(
    {
      // Set the default scope to transient
      // (and in this case, cannot be overridden)
      scope: ProviderScope.Transient,
    },
    {
      beforeProvide: console.log,
      afterProvide: console.log,
    },
  )(options);
};

@Service({ id: 'foo' })
class Foo {}