NestJS tip: multi-value providers almost like `multi` from Angular
Tags: node, nestjs, typescript, angular
Published: 2023-02-05 17:17:13

> for NestJS v8, v9 and v10

You can see and play with the full code [here](https://stackblitz.com/edit/nestjs-typescript-starter-vh9xui?file=src%2Fapp.module.ts).

---

Angular's dependency injection mechanism has a feature called [Multi Providers](https://angular.io/api/core/ClassProvider#multi-value-example), which looks like this:

```ts
// =============== Angular code  =============== //
import { Component, InjectionToken, Inject } from '@angular/core'
const TOKEN = new InjectionToken<string>('MyToken')
@Component({
  // ...
  providers: [
    { provide: TOKEN, useValue: 'foo', multi: true },
    { provide: TOKEN, useValue: 'bar', multi: true },
  ],
})
class AppComponent {
  constructor(@Inject(TOKEN) public myValues: string[]) {
    console.log(myValues) // outputs ["foo", "bar"]
  }
}
```

NestJS doesn't come with that capability but we can implement it (kinda).

The final version I'm proposing here looks like this:

![final result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uwvy9k2zcgxhtx19udy8.png)

![module definition](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g99nkk7inbp1lhqrdeqx.png)

![terminal output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2q1wjw5juoxrpb2s0dlu.png)

we have a factory function called `provide` that receives an array of 'enhanced' providers and return providers array but registering all providers with the same token under a multi-value provider as long as it has `multi: true`.

### Disclaimer

The solution I'm about to show is intended to be as **simple as possible**, thus it doesn't work exactly like multi providers from Angular. I encourage you to use this mental model to build a better solution :)

Known limitations:

- Only merges providers registered within the same module
- Does not validate if there is a multi provider with the same token as one non-multi
- Does not take in count providers with scopes other than the default (singleton lifestyle). I didn't tested such scenario
- Dependency not found errors may look a bit cryptic now due to how the provider is being registered under the hood
- Not work with `forwardRef`, but it should be feasible to have

## Problem

NestJS providers consists of two main parts: a value and a unique token. We can see that with custom providers, which are just objects that have the properties `provide` (provider's token) and some value that will be defined based on which kind of provider you got (namely: `useClass`, `useExisting`, `useValue` or `useFactory`). You can learn more on this subject [here](https://dev.to/nestjs/advanced-nestjs-dynamic-providers-1ee).

To retrieve some provider NestJS's DI system uses its token (in a given context). So although we can declare multiple providers with the same token, only the latest one will be returned when we inject that provider into others providers.

We want to register multiple providers under the same token, and we should have an easy API for this. When retrieving that provider, we should get an array of values.

## Solution

Leverage on `multi: true` API from Angular and implement one version of that feature by using the [`useFactory`](https://docs.nestjs.com/fundamentals/custom-providers#factory-providers-usefactory) custom provider as suggested by Kamil [here](https://github.com/nestjs/nest/issues/770#issuecomment-1412029060).

The overall ideia here is:  
1. Group all providers marked with `multi: true` by their common token
2. For each provider in that group, register it using some unique token to avoid naming collisions. And save this token in that group for later usage
3. For each entry in that group, register a new provider with `useFactory` that injects all of the collected providers at step (2) and return an array of them

I'll do only 2 iterations for performance sake: one to collect and save multi-value providers while registering non-multi ones, as usual; and other to create the final factory provider for each group.

The `provide` helper function is defined as follows:

```ts
// provide.util.ts
import type {
  Type,
  ClassProvider,
  ValueProvider,
  FactoryProvider,
  ExistingProvider,
  Provider,
  InjectionToken,
} from "@nestjs/common"

type EnhancedProvider<T = any> =
  Type<T>
| ((
    ClassProvider<T> |
    ValueProvider<T> |
    FactoryProvider<T> |
    ExistingProvider<T>
  ) & { multi?: true })

export function provide(providers: EnhancedProvider[]): Provider[] {
  /** The final providers list that we should pass to some module. */
  const providersToRegister: Provider[] = []
  /** A map with all multi-value providers with their common token. */
  const multiValueProviderTokensByGroupToken = new Map<InjectionToken, InjectionToken[]>()

  for (const provider of providers) {
    if ('multi' in provider && provider.multi) {
      const currProviderToken = provider.provide

      const providerTokens = multiValueProviderTokensByGroupToken.get(currProviderToken) || []

      // Create a unique token for this provider so that it can be injected later
      const uniqueToken: InjectionToken = `multi-provider at idx ${providerTokens.length + 1} with token ${currProviderToken.toString()}`
      providerTokens.push(uniqueToken)
      multiValueProviderTokensByGroupToken.set(currProviderToken, providerTokens)

      // Register the provider but using the unique token instead
      providersToRegister.push({
        ...provider,
        provide: uniqueToken,
      })
    } else {
      // Non-multi provider, so just register it as-is
      providersToRegister.push(provider)
    }
  }

  for (const [providerGroupToken, tokensToInject] of multiValueProviderTokensByGroupToken) {
      providersToRegister.push({
      provide: providerGroupToken,
      inject: tokensToInject,
      useFactory: (...providers) => providers,
    })
  }

  return providersToRegister
}
```