Resolvers

Getting started

Internally, the DI Container figures out how to locate each dependency using a Resolver. Which resolver is used depends on if or how the dependency was registered. (See below for registration information) However, you can specify a special resolver when you declare your dependencies.

For example, perhaps your application has an Inspector composed of Panel instances. You may want to inject the panels into the inspector. However, there isn't just one Panel; multiple different types of panels implement the Panel interface. In this case, you'd want to use the all resolver. Here's what that would look like with a plain class:

import { all } from 'aurelia';

export class Inspector {
  static inject = [all(Panel)];
  constructor(private panels: Panel[]) { }
}

And with a decorator...

import { all } from 'aurelia';

@inject(all(Panel))
export class Inspector {
  constructor(private panels: Panel[]) { }
}

In another scenario, you might depend on a service that is expensive to create, which isn't always required, depending on what the user is doing with the app. In this case, you may want to resolve the dependency when it's needed lazily. For this, use the lazy resolver:

import { lazy } from 'aurelia';

export class MyClass {
  static inject = [lazy(ExpensiveToCreateService)];
  constructor(private getExpensiveService: () => ExpensiveToCreateService) {}
}

And with a decorator...

import { lazy } from 'aurelia';

@inject(lazy(ExpensiveToCreateService))
export class MyClass {
  constructor(private getExpensiveService: () => ExpensiveToCreateService) {}
}

In this case, the lazy resolver will inject a function that you can call on demand to get an instance of the ExpensiveToCreateService.

The core DI container does not enable circular dependencies. To get around this issue, in situations where you might have a circular reference, you can use a lazy resolver.

Custom resolvers

You can create your own resolver by implementing the IResolver interface. Here's what that looks like:

export interface IResolver<T = any> {
  resolve(handler: IContainer, requestor: IContainer): T;
  getFactory?(container: IContainer): IFactory<T> | null;
}

As you can see, you only need to implement one method, which uses the container (either the original requesting container or the one that the request bubbled up to, if they aren't the same) to return the instance. Here's a simplified version of the all built-in function:

function all(key) {
  return {
    resolve(handler: IContainer, requestor: IContainer) {
      return requestor.getAll(key);
    }
  }
}

And here's a simplified version of lazy:

function lazy(key) {
  return {
    resolve(handler: IContainer, requestor: IContainer) {
      let lazyInstance = null;

      return () => {
        if (lazyInstance === null) {
          lazyInstance = requestor.get(key);
        }

        return lazyInstance;
      };
    }
  }
}

Note You can additionally implement getFactory to return a factory capable of creating instances of what this resolver will resolve. This is implemented by the standard internal resolver so that it's always possible to get the factory for singleton/transient registrations.

Last updated

Was this helpful?

Revision created

chore(slotted): add reminder doc about standard