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)