for NestJS v8, v9 and v10
You can see and play with the full code here.
Angular's dependency injection mechanism has a feature called Multi Providers, which looks like this:
// =============== 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:
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 - as mentioned in the comments of this post, you cannot use this when sharing the same provider token for multiple modules
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.
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
custom provider as suggested by Kamil here.
The overall ideia here is:
- Group all providers marked with
multi: true
by their common token - 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
- 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:
// 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
}
Top comments (5)
This solution works in one stand-alone module, but does not work if you need to import that module into a current module that already has a multi-provider with a shared token.
That is, something like this (pseudocode):
You don't show a usage example, but from the source code it looks like you need to call
provide(myProviders)
, and it will simply group those withmulti: true
and the same token, but that ultimately doesn't offer much advantages over justproviders: [{provide: token, useFactory: (...args) => args, inject: [...myProviders]}]
and its associated drawbacks: it doesn't work across module boundaries, and requires you to know all the providers beforehand, correct me if I'm wrong.I created a package that works more closely to Angular's
multi
provider, you can provide the same token multiple times in different modules, and then callcollect(TOKEN)
in any module where you'd like to have aTOKEN
provider that is equal to the array of individual provided values.github.com/Helveg/nestjs-multi-pro...
Your package does not work with exports and imports at the moment (
nestjs-multi-provider
v0.3.2).I don't have a clue, actually.
We have this linked at github.com/nestjs/awesome-nestjs and I try to bring more popularity to here in nestjs telegram groups.
Maybe its because people aren't used with dev.to tags or dev.to in general (I didn't checked how's the popularity of
express
here)?Also, we should keep in mind that the downloads rate at NPM didn't exclude CIs.
I don't :/ dev.to is my favorite one.
My view stats on this post is too low (< 25). But as of now I got 433 at the first post I've made.