# Custom providers

There are a lot of scenarios where you might want to bind something directly to the IoC container. For example, any constant values, configuration objects created based on the current environment, external libraries, or pre-calculated values that depend on few other defined providers.

Moreover, you are able to override default implementations, e.g. use different classes or make use of various test doubles (for testing purposes) when needed.

One essential thing that you should always keep in mind is that Ts.ED uses TokenProvider to identify a depencency.

Usually, the auto-generated tokens are equal to classes. If you want to create a custom provider, you'd need to choose a token. Mostly, the custom tokens are represented by either plain strings or symbols.

Let's go through the available options.

# Use Value

The useValue syntax is useful when it comes to either define a constant value, put external library into DI container, or replace a real implementation with the mock object.

import {registerProvider} from "@tsed/di";
import {connection} from "connection-lib";

export const CONNECTION = Symbol.for("CONNECTION");

registerProvider({
  provide: CONNECTION,
  useValue: connection,
  hooks: {
    $onDestroy(connection: any) {
      return connection.close();
    }
  }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14

In order to inject custom provider, we use the Inject decorator. This decorator takes a single argument - the token.

import {Inject, Injectable} from "@tsed/di";
import {CONNECTION} from "./connection";

@Injectable()
export class MyService {
  constructor(@Inject(CONNECTION) connection: any) {}
}
1
2
3
4
5
6
7

# Use Factory

The useFactory is a way of creating providers dynamically. The actual provider will be equal to a returned value of the factory function. The factory function can either depend on several different providers or stay completely independent. It means that factory may accept arguments, that DI will resolve and pass during the instantiation process.

import {Configuration, registerProvider} from "@tsed/di";
import {DatabaseConnection} from "connection-lib";

export const CONNECTION = Symbol.for("CONNECTION");

registerProvider<DatabaseConnection>({
  provide: CONNECTION,
  deps: [Configuration],
  useFactory(configuration: Configuration) {
    const options = configuration.get<any>("myOptions");

    return new DatabaseConnection(options);
  },
  hooks: {
    $onDestroy(connection) {
      return connection.close();
    }
  }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

In order to inject a custom provider, we use the Inject decorator. This decorator takes a single argument - the token.

import {Inject, Injectable} from "@tsed/di";
import {CONNECTION} from "./connection";

@Injectable()
export class MyService {
  constructor(@Inject(CONNECTION) connection: any) {}
}
1
2
3
4
5
6
7

TIP

Since v6.110.0, factory and custom provider can register his own hooks!

# Use Async Factory

The useAsyncFactory is a way of creating asynchronous providers dynamically. The actual provider will be equal to a returned value of the factory function. The factory function can either depend on several different providers or stay completely independent. It means that factory may accept arguments, that DI will resolve and pass during the instantiation process.

import {Configuration, registerProvider} from "@tsed/di";
import {DatabaseConnection} from "connection-lib";

export const CONNECTION = Symbol.for("CONNECTION");

registerProvider({
  provide: CONNECTION,
  deps: [Configuration],
  async useAsyncFactory(settings: Configuration) {
    const options = settings.get("myOptions");
    const connection = new DatabaseConnection(options);

    await connection.connect();

    return connection;
  }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

In order to inject a custom provider, we use the Inject decorator. This decorator takes a single argument - the token.

import {Inject, Injectable} from "@tsed/di";
import {CONNECTION} from "./connection";

@Injectable()
export class MyService {
  constructor(@Inject(CONNECTION) connection: any) {}
}
1
2
3
4
5
6
7

WARNING

Async factory will always be considered as SINGLETON. It is not possible to use other scopes like REQUEST and INSTANCE because asynchronous providers are resolved on server loading.

# Use Class

The useClass syntax is similar to register provider via decorator. But it allows you to use different classes per chosen factors. For example you can change the class depending on the environment profile production or development.

import {EnvTypes} from "@tsed/core";
import {registerProvider} from "@tsed/di";

export class ConfigService {}

export class DevConfigService {}

registerProvider({
  provide: ConfigService,
  useClass: process.env.NODE_ENV === EnvTypes.PROD ? ConfigService : DevConfigService
});
1
2
3
4
5
6
7
8
9
10
11

TIP

registerProvider can be used to add a provider or override an existing provider (like OverrideProvider decorator).

In this case, even if any class depends on ConfigService, Ts.ED will inject an instance of the provided class (ConfigService or DevConfigService) instead.

import {Injectable} from "@tsed/di";
import {ConfigService} from "./ConfigService";

@Injectable()
export class MyService {
  constructor(configService: ConfigService) {
    console.log(process.env.NODE_ENV, configService); // DevConfigService or ConfigService
  }
}
1
2
3
4
5
6
7
8
9

Last Updated: 10/24/2024, 6:33:40 AM

Other topics