Resolvers

Resolvers in Aurelia 2 are integral to the Dependency Injection (DI) system, providing various strategies for resolving dependencies. This guide will cover each resolver type, its usage, and when to use it, with detailed code examples for both the @inject decorator and static inject property methods. Additionally, we will discuss how to create custom resolvers.

Built-in Resolvers

Aurelia 2 offers several built-in resolvers to address different dependency resolution needs. Here's how to use them with both the @inject decorator and static inject property.

lazy Resolver

Use the lazy resolver when you want to defer the creation of a service until it's needed. This is particularly useful for expensive resources.

Using @inject Decorator

import { lazy, inject } from 'aurelia';

@inject(lazy(MyService))
export class MyClass {
  constructor(private getMyService: () => MyService) {
    // Call getMyService() when you need an instance of MyService
  }
}

Using Static inject Property

import { lazy } from 'aurelia';

export class MyClass {
  static inject = [lazy(MyService)];
  constructor(private getMyService: () => MyService) {
    // Similar usage as with the decorator
  }
}

all Resolver

The all resolver is used to inject an array of all instances registered under a particular key. This is useful when working with multiple implementations of an interface.

Using @inject Decorator

import { all, inject } from 'aurelia';

@inject(all(MyService))
export class MyClass {
  constructor(private services: MyService[]) {
    // services is an array of MyService instances
  }
}

Using Static inject Property

import { all } from 'aurelia';

export class MyClass {
  static inject = [all(MyService)];
  constructor(private services: MyService[]) {
    // Similar usage as with the decorator
  }
}

optional Resolver

The optional resolver allows a service to be injected if available, or undefined if not. This can prevent runtime errors when a dependency is not critical.

Using @inject Decorator

import { optional, inject } from 'aurelia';

@inject(optional(MyService))
export class MyClass {
  constructor(private service?: MyService) {
    // service is MyService or undefined
  }
}

Using Static inject Property

import { optional } from 'aurelia';

export class MyClass {
  static inject = [optional(MyService)];
  constructor(private service?: MyService) {
    // Similar usage as with the decorator
  }
}

factory Resolver

The factory resolver provides a function to create instances of a service, allowing for more control over the instantiation process.

Using @inject Decorator

import { factory, inject } from 'aurelia';

@inject(factory(MyService))
export class MyClass {
  constructor(private createMyService: () => MyService) {
    // createMyService is a function to create MyService instances
  }
}

Using Static inject Property

import { factory } from 'aurelia';

export class MyClass {
  static inject = [factory(MyService)];
  constructor(private createMyService: () => MyService) {
    // Similar usage as with the decorator
  }
}

newInstanceForScope Resolver

Use newInstanceForScope when you need a unique instance of a service within a particular scope, such as a component or sub-container.

Using @inject Decorator

import { newInstanceForScope, inject } from 'aurelia';

@inject(newInstanceForScope(MyService))
export class MyClass {
  constructor(private service: MyService) {
    // service is a new scoped instance of MyService
  }
}

Using Static inject Property

import { newInstanceForScope } from 'aurelia';

export class MyClass {
  static inject = [newInstanceForScope(MyService)];
  constructor(private service: MyService) {
    // Similar usage as with the decorator
  }
}

newInstanceOf Resolver

The newInstanceOf resolver ensures that a fresh instance of a service is created each time, regardless of other registrations.

Using @inject Decorator

import { newInstanceOf, inject } from 'aurelia';

@inject(newInstanceOf(MyService))
export class MyClass {
  constructor(private service: MyService) {
    // service is a fresh instance of MyService
  }
}

Using Static inject Property

import { newInstanceOf } from 'aurelia';

export class MyClass {
  static inject = [newInstanceOf(MyService)];
  constructor(private service: MyService) {
    // Similar usage as with the decorator
  }
}

last Resolver

The last resolver is used to inject the last instance registered under a particular key. This can be useful when you need the most recently registered instance among multiple registrations of the same key.

Using @inject Decorator

import { last, inject } from 'aurelia';

@inject(last(MyService))
export class MyClass {
  constructor(private service: MyService) {
    // service is the last registered instance of MyService
  }
}

Using Static inject Property

import { last } from 'aurelia';

export class MyClass {
  static inject = [last(MyService)];
  constructor(private service: MyService) {
    // service is the last registered instance of MyService
  }
}

Example

If you have multiple instances of a service registered under the same key, last will ensure that you get the most recently registered instance:

import { DI, IContainer, last, Registration } from 'aurelia';

const container = DI.createContainer();
container.register(Registration.instance(MyService, new MyService('instance1')));
container.register(Registration.instance(MyService, new MyService('instance2')));
container.register(Registration.instance(MyService, new MyService('instance3')));

const myClass = container.get(last(MyService));
console.log(myClass.service); // Outputs: instance3

In this example, myClass.service will be the instance of MyService registered last (i.e., instance3).

If no instances are registered under the specified key, the last resolver will return undefined:

const container = DI.createContainer();

const myClass = container.get(last(MyClass));
console.log(myClass.service); // Outputs: undefined

Custom Resolvers

You can create custom resolvers by implementing the IResolver interface. Custom resolvers give you the flexibility to implement complex resolution logic that may not be covered by the built-in resolvers.

Example of a Custom Resolver

import { IResolver, IContainer, inject } from 'aurelia';

class MyCustomResolver<T> implements IResolver {
  $isResolver: true;
  constructor(private key: new (...args: any[]) => T) {}

  resolve(handler: IContainer, requestor: IContainer): T {
    // Custom resolution logic here
    return new this.key();
  }
}

// Usage
@inject(new MyCustomResolver(MyService))
export class MyClass {
  constructor(private service: MyService) {
    // service is resolved using MyCustomResolver
  }
}

In the example above, MyCustomResolver is a custom resolver that creates a new instance of MyService. You can further customize the resolve method to suit your specific requirements.

By understanding and utilizing these resolvers, you can achieve a high degree of flexibility and control over the dependency injection process in your Aurelia 2 applications. The examples provided illustrate how to apply each resolver using both the @inject decorator and static inject property, giving you the tools to manage dependencies effectively in any situation.

Last updated