NestJS - how to provide services of dynamic modules - javascript

I'm struggling to figure out on how to provide services from DynamicModule to regular Modules. Pseudocode below:
app.module.ts
#Global()
#Module({
imports: [
DynamicModule.forRoot(config),
RegularModule,
],
providers: [],
exports: [],
})
export class AppModule {}
dynamic.module.ts
#Module({})
export class DynamicModule implements OnModuleInit, OnModuleDestroy {
constructor(private dynamicService: dynamicService) {}
static forRoot(config: Config): DynamicModule {
return {
module: DynamicModule,
imports: [],
providers: [
{
provide: CONFIG_TOKEN,
useValue: config,
},
DynamicService,
],
exports: [
DynamicService,
],
};
}
}
dynamic.service.ts
#Injectable()
export class DynamicService {
constructor(
#Inject(CONFIG_TOKEN) private readonly config: Config,
) {}
}
regular.module.ts
#Module({
imports: [],
providers: [RegularService, DynamicService],
exports: [RegularService],
})
export class RegularModule {}
regular.service.ts
#Injectable()
export class RegularService {
constructor(
private readonly dynamicService: DynamicService
) {}
}
Providing DynamicService to RegularModule requires to provide CONFIG_TOKEN in RegularModule as well, which seems odd and not practical in case more modules would depend on DynamicService and doesn't seem to be the correct way.
What concepts am I missing and what is correct approach to use services of a DynamicModule?
Would something as forFeature in DynamicModule method would be the right direction?

dynamic modules are modules that need some sort of context defined input, classic example is a database module that would need at least the host url and credentials, which would vary by app.
This means that when the forRoot(input) returns, you have a module just like any regular (non dynamic) module. As such, you can make use of the config value inside the dynamic module's service, export the service on the dynamic module, and then inject that service on other modules that import your dynamic module.
There is no need to also inject the config value on the service that injected the dynamicService.
If you need to have direct access to the config value inside of regularService, and that value is being shared across multiple services and modules, then you should take a look at the ConfigModule and treat that config value as env. If by some very specific reason it cant or should not be in env, then still you should create a separate module for providing this config values.

Related

Nest can't resolve dependencies of the X. Please make sure that the argument HttpService at index [1] is available in the Module context

I have a problem while was working with nestjs HttpModule
The error:
Nest can't resolve dependencies of the ShopService (PrismaService, ?). Please make sure that the argument HttpService at index [1] is available in the QaModule context.
My ShopModule:
#Module({
imports: [HttpModule],
controllers: [ShopController],
providers: [ShopService, PrismaService],
})
export class ShopModule {}
My QaModule:
#Module({
controllers: [QaController],
providers: [QaService, PrismaService, ShopService],
})
export class QaModule {}
What are the solutions?
Change your ShopModule to
#Module({
imports: [HttpModule],
controllers: [ShopController],
providers: [ShopService, PrismaService], // Prisma Service should probably come from PrisamModule
exports: [ShopService],
})
export class ShopModule {}
and your QaModule to
#Module({
imports: [ShopModule],
controllers: [QaController],
providers: [QaService, PrismaService], // Prisma Service should still probably come from `PrismaModule`
})
export class QaModule {}
Services should only be declared in one module and then exported from that module if they need to be used elsewhere. In the module that declares the consuming service, the original module should be added to the imports array. This will ensure you have only one instance of each non-transient provider and will not need to be recreated in each module it is used in

How to properly inject provider in NestJS?

I create a Firebase provider like below(without using module file with exports and imports):
#Injectable()
export class FirebaseProvider {
public app: admin.app.App;
constructor() {
this.app = admin.initializeApp(config);
}
}
and add this provider to providers in AppModule:
providers: [AppService, FirestoreProvider, ClientService],
and in ClientModule:
providers: [CustomersService, FirestoreProvider],
so, now i want to use this provider in my ClientService, so I inject this provider via constructor like below:
constructor(
private readonly firestoreProvider: FirestoreProvider,
) {}
and now i have a problem, can somone tell me why NestJs inject me this provider more than one time and because of this firebase throw me this error:
The default Firebase app already exists. This means you called initializeApp() more than once without providing an app name as the second argument. In most cases you only need to call initializeApp() once. But if you do want to initialize multiple
apps, pass a second argument to initializeApp() to give each app a unique name.
I initialize this firebase only in this provider, so how to properly use this provider in services across the whole project?
thanks for any help!
I don't think you want to declare your FirestoreProvider in the provider:[] array of a module more than once. Try something like this:
#Module({
imports: [
FirestoreModule
],
})
export class AppModule{}
Now that you've imported the FirestoreModule, you can use it in any class that is in this AppModule. Example:
#Injectable()
export class FooService {
constructor(private firestore: FirestoreProvider) {}
}
The key here is to define your provider in its own module, then export it via the exports:[] array, and provide it via the providers:[] array of that module.
import { FirestoreProvider } from './FirestoreProvider'
#Module({
providers: [ FirestoreProvider ],
exports: [ FirestoreProvider ],
})
export class FirestoreModule{}
#Injectable()
export class FirestoreProvider {
public app: admin.app.App;
constructor() {
this.app = admin.initializeApp(config);
}
}

How to export functions in shared.module?

I have the following function:
export function toCamelCase(string): string {
//code omitted
}
Then I use it on my feature components by adding reference as:
import * as utility from '../shared/functions/helper-functions';
However, I use a shared module and I am wondering if it is possible to add this helper function reference only to shared module and export it so that I can use its functions without adding its reference (I already add shared.module reference). Any idea?
import * as utility from '../shared/functions/helper-functions';
#NgModule({
declarations: [
//...
],
imports: [
//...
],
exports: [
// I want to add helper function as a module
],
providers: [
//...
})
export class SharedModule { }
Just export class SharedModule and export * from '../shared/functions/helper-functions' as well.
If you really want to expose the helper functions in the Angular module, you could expose them as services (in the providers section) but that wouldn't really make sense.

How do I get the entryComponents dynamically using import() method of Typescript 2.4

Typescript 2.4 introduce [import()][1] method to load modules dynamically. I am trying to load components dynamically using the following mentioned procedure https://angular.io/guide/dynamic-component-loader where we need to mention entryComponents in the module.
I am able to load all component modules dynamically using import() method in the AppModule but I am not able to get entryComponents of the modules. For example,
I have a component and its module
data-widget.component:
#Component({
selector: 'data-widget',
templateUrl: 'data-widget.component.html'
})
export class DataWidget{}
and
data-widget.module.ts
#NgModule({
imports: [ ...,
declarations: [DataWidget],
exports: [DataWidget],
providers: [],
entryComponents: [DataWidget]
})
export class DataWidgetModule {
constructor(private widgetService: widgetService) {
widgetService.register('weather', DataWidget);
}
}
I am now able to load that module dynamically using import() method in the following way
app.module.ts
#NgModule({
imports: [ ...,
declarations: [],
exports: [],
providers: []
})
export class AppModule {
constructor(private apiService: apiService) {
apiService.getMoudleUrls().subscribe( data=>{
import(''+data); //e.g './data-widget/data-widget.module'
},
error=>{});
}
}
In AppModule module the DataWidgetModule loaded in Webpack dynamically.Now I am trying to load DataWidget Component dynamically. You can see that I have register the component in DataWidgetModule using widgetService. But this function was not called during module loading as well as the entryComponents are also not loaded. So, when I tried to load the component using the following way which I have mentioned above
this.componentFactoryResolver.resolveComponentFactory(widgetService.getRegisteredWidget());
then I am getting error as entryComponents are not loaded in #ngModule. How can I load the entryComponents and register the components for dynamic load? It will be very helpful to get a solution for it. Thank you.
I don't know if this is the right topic, but I had a similar problem and found a little hacky solution.
The problem was that I wanted to use the entry components of a new compiled module inside the parent module. The entry components of the loaded and compiled module are present only in the compiled module so I had to add them to the parent module entry components.
Just add the entry components factories to the parent component factories.
const resolver = moduleRef.componentFactoryResolver;
resolver._factories.forEach(function (value, key) {
resolver._parent._factories.set(key, value);
});
In my case I had only a few components and entry components in the loaded module so it is no overhead to merge them with the parent component factories. For larger loaded modules I think it would be good to only merge the entry components...
I find a solution for it. The following code will help you to solve this problem.
export class DynamicComponent {
injector: Injector;
compiler: Compiler;
#ViewChild('container', {read: ViewContainerRef})
container: ViewContainerRef;
componentRef: any;
constructor(private compFactoryResolver: ComponentFactoryResolver, injector: Injector,
private apiService: apiService) {
this.injector = ReflectiveInjector.resolveAndCreate(COMPILER_PROVIDERS, injector);
this.compiler = this.injector.get(Compiler);
}
addWidget(){
apiService.getMoudleUrls().subscribe( module_url=>{
let module_= import(module_url); //e.g './data-widget/data-widget.module'
module_.then(module_data =>{
const keys = Object.getOwnPropertyNames( module_data );
let moduleFactories = this.compiler.compileModuleAndAllComponentsSync(module_data[keys[1]]);
const moduleRef = moduleFactories.ngModuleFactory.create(this.injector);
const componentFactory = moduleFactories.componentFactories
.find(e => e.selector === 'data-widget'); // find the entry component using selector
this.componentRef = this.container.createComponent(componentFactory, null, moduleRef.injector);
const newItem: WidgetComponent = this.componentRef.instance;
},
error=>{});
}
}

Force service instantiation in Angular 2 sub-module (an alternative to AngularJS run block)

I have a service in sub-module that wraps some third-party module, instantiates and initializes its service to prepare for use within app.
#Injectable()
class SubmoduleInitializerService {
constructor (thirdPartyService: ThirdPartyService) {
thirdPartyService.initialize(...);
...
}
}
#NgModule({
imports: [ThirdPartyModule],
exports: [ThirdPartyModule],
providers: [
ThirdPartyService,
SubmoduleInitializerService
]
})
class AppSubmodule {}
ThirdPartyService isn't injected in app directly but is used by other ThirdPartyModule units, so as long as SubmoduleInitializerService is injected in the same injector as ThirdPartyService or parent injector, everything is fine:
export class AppComponent {
constructor(
/* DO NOT REMOVE! BAD THINGS HAPPEN! */
submoduleInitializerService: SubmoduleInitializerService
) {}
...
}
It was proven to be a lousy pattern because it is not obvious why SubmoduleInitializerService should stay injected in AppComponent if it's not used neither in class nor in template (was accidentally removed once already).
Basically AppSubmodule module needs an alternative to Angular 1.x angular.module(...).run(...) block.
What are the options here?
APP_INITIALIZER (undocumented) service plays the role of AngularJS config/run blocks reasonably well in Angular 2 (not counting the feature of asynchronous initialization).
For noop intialization block that just eagerly instantiates SubmoduleInitializerService it is:
#NgModule({
imports: [ThirdPartyModule],
exports: [ThirdPartyModule],
providers: [
ThirdPartyService,
SubmoduleInitializerService,
{
provide: APP_INITIALIZER,
useFactory: () => () => {},
deps: [SubmoduleInitializerService],
multi: true
}
]
})
class AppSubmodule {}
Since APP_INITIALIZER is multi-provider, it allows to have several initialization functions per application that follow the order in which the modules are being loaded.
For synchronous initialization the shorter (and probably more appropriate) alternative is to inject the service into module's constructor:
#NgModule({
imports: [ThirdPartyModule],
exports: [ThirdPartyModule],
providers: [
ThirdPartyService,
SubmoduleInitializerService
]
})
class AppSubmodule {
constructor(sis: SubmoduleInitializerService) {}
}
As explained in this answer, APP_INITIALIZER shares some traits with config block, too, because it is used to configure services prior to component initialization and is susceptible to race conditions (for example, since APP_INITIALIZER is used to configure Router, injecting it into another APP_INITIALIZER will result in circular dependency).

Categories