1
  • How does the framework manage the lifetime of DynamicModules?

The NestJs documentation on Modules states that:

In Nest, modules are singletons by default, and thus you can share the same instance of any provider between multiple modules effortlessly.

  • How can you share multiple dynamic module instances between modules?

The NestJs documentation on DynamicModules states that:

In fact, what our register() method will return is a DynamicModule. A dynamic module is nothing more than a module created at run-time, with the same exact properties as a static module, plus one additional property called module.

  • How can you manage/change the scope of DynamicModules? For example, changing them from behaving transitively to as a singleton. Defining their injection token, retrieving them on demand, etc.
Francisco Aguilera
  • 3,099
  • 6
  • 31
  • 57

1 Answers1

1

How does the framework manage the lifetime of DynamicModules?

Generally speaking, like it does any other module. A dynamic module is just a special name for a module configuraed by a function and represented by an object. The end result is usually something like

{
  module: SomeModuleClass,
  imports: [Imports, For, The, Module],
  providers: [SomeProviderToken, SomeProviderService, ExtraProvidersNeeded],
  exports: [SomeProviderService],
}

Pretty much the same kind of thing you'd see in an @Module() decorator, but configured via a function that possibly uses DI instead of just written directly

How can you share multiple dynamic module instances between modules?

I might need a bit of clarification here, but I'll be happy to edit my answer with more detail once I know what's the goal here, or what you're trying to do.

How can you manage/change the scope of DynamicModules? For exmaple, changing them from behaving transitively to as a singleton. Defining their injection token, retrieving them on demand, etc.

The easiest option for sharing your configuration (besides making the module @Global()) is to make a wrapper module that re-exports the dynamic module once it has been configured.

Example: Let's say we have a dynamic FooModule that we want to pass application to, to designate the name of the application, and we want to re-use that module in several other places

@Module({
  imports: [
    FooModule.forRoot(
      { application: 'StackOverflow Dynamic Module Scope Question' }
    )
  ],
  exports: [FooModule],
})
export class FooWrapperModule {}

Now instead of importing FooModule.forRoot() again in multiple places, we just import FooWrapperModule and get the same instance of FooService with the configuration originally passed.

I do want to mention that by convention, DynamicModule.forRoot/Async() usually implies single time registration in the RootModule and usually has a @Global() or isGlobal: true config attached to it somewhere. This isn't always the case, but it holds relatively true.

DynamicModule.register/Async() on the other hand, usually means that we are configuring a dynamic module for this module only and it can be reconfigured elsewhere to have it's own separate config. This can lead to cool setups where you can have multiple JwtService instances that have different secret values for signing (like for an access and refresh token signing service).

Then there's DynamicModule.forFeature() which is like register in that it is at a per module basis, but usually it uses config from the forRoot/Async() call that was already made. The @nestjs/typeorm module, mikro-orm/nestjs module, and @ogma/nestjs-module module are three separate examples I can think of that follow this pattern. This is a great way to allow for general configuration at the root level (application name, database connection options, etc) and then allow for scoped configuration at the module level (what entities will be injected, logger context, etc)

Jay McDoniel
  • 57,339
  • 7
  • 135
  • 147
  • **"The easiest option for sharing your configuration (besides making the module @Global()) is to make a wrapper module that re-exports the dynamic module once it has been configured."** But if you wrap the module in a "static module" then you can't share data with the DynamicModule that is not available within it's context (as below). `@Module({ imports: [ MyLibDynModule.register(some data available in App but not in Lib); ], }) export class AppModule {}` However, I think @Global solves that problem. – Francisco Aguilera Feb 05 '22 at 01:44
  • 1
    I'm not sure I follow. The idea of a dynamic module is to be configurable, right? If you want to re-use that configuration easily, then wrapping the module is a very easy approach that doesn't compromise the original dynamic module. If you need to register a main config in one place, and re-use it along with extra configuration, the `forRoot`+`forFeature` approach sounds like what you'd need. [I have a good example of that here](https://github.com/jmcdo29/ogma/blob/main/packages/nestjs-module/src/ogma.module.ts) – Jay McDoniel Feb 05 '22 at 02:01
  • It's hard to explain in 500 characters with no formatting, but among others, my comment above is referring to one of the solutions I was looking for which was: passing NX's 'environment.ts' variables (which lives in the Nx NestJs app) into a NestJs lib module. The lib module is not part of the NestJs application project and adding a reference from the Lib to the project would cause a circular dependency as the application references the Lib (for it's services). – Francisco Aguilera Feb 05 '22 at 02:24
  • 2
    I don't understand why more power around the Module scope isn't given to NestJs developers in a simpler way than wrapping modules, etc. Just give us a parameter in the `@Module` decorator where we can define the Module's scope. We don't need more boilerplate or decorators, etc. – Francisco Aguilera Feb 05 '22 at 02:26
  • Also I'm assuming that in the case where you want a Module to behave in both a Transient (different module instance every time one is requested) or Singleton (One shared module given every time it's requested) manner, you just have to split it into two modules where one behaves like a Singleton and the other Transient, with maybe some shared logic in the form of services or re-imported Typescript modules. – Francisco Aguilera Feb 05 '22 at 02:29
  • 1
    [If you want to discuss more on Discord, I'm rather active there](https://discord.gg/nestjs). I don't see why the library would need configuration coming from the main application, or what kind of power you'd be expecting to find, but you're right that it's hard to explain in so few characters – Jay McDoniel Feb 05 '22 at 02:30
  • 1
    Your post was very thorough, thanks. But if I have any doubts I'll be sure to connect with someone there. :) – Francisco Aguilera Feb 05 '22 at 02:38
  • And here's a question I answered on dealing with those NX environment variables: https://stackoverflow.com/questions/70908879/nx-nestjs-sharing-nx-environment-variables – Francisco Aguilera Feb 05 '22 at 02:49