#Dynamic Module async

1 messages · Page 1 of 1 (latest)

craggy bridge
#

Hey guys!
I'm new to NestJS world, so i'm struggling a bit with something that might be trivial for you all.
I have the following scenario:

  • I have a lib(built in typescript), this lib is "mainly" a class, let's call it XPTOService. XPTOService main purpose is to make some SOAP calls, using soap library, which needs to instantiate a new Client in an async way.
  • This XPTOService has a method called build which will create a new instance of the Client class from soap pkg
  • This service has some other methods that will use that instance to make some SOAP calls

On the other side, i have my NestJS server, which will consume that library. I created a service, that will use that library, and access its available methods.
My problem here is, i need to initialize the lib first(calling the build method) before i can call any other method that relies on the Client instance, I've done some researches regarding Dynamic Module, but none of those articles that i read have a specific case like mine.

Anyone here knows how can i instantiate that lib inside my Service,calling the build method, and enable the whole application to use it?

low island
#

Sounds like all you need is a custom "factory provider" that will instantiate your soap client, which you can then inject to your service that will be exported from your custom library module.

junior cove
#

TIL that the factory is also singleton scoped, I thought it was scoped to transient. Always used module hooks to do these kind of things

craggy bridge
#

Could you guys explain that better? Again, i'm new here, and i thought this was something simple to accomplish

junior cove
#

They are saying that you can register it as a singleton provider e.g. (psuedo)

@Module({
  providers: [{
    useFactory: async () => {
      const soap = new Soap()
      await soap.build()

      return soap
    },
    provide: SomeToken
  }]
})
class AppModule {}
#

and then you simply inject it in your constructor as usual

low island
junior cove
low island
junior cove
#

Figured. Good to know!

craggy bridge
#

I think i got it, but i have the following scenario:

  • Controller > Service > Lib that needs to run build before its usage

I'm injecting the that lib inside the service, so i need that service to be loaded by NestJS and calls build from the lib, in that order

#

So my controller can call its methods

#

Let me know if i wasn't clear enough

#

I have something like this:

// My module
@Module({
  imports: [],
  providers: [
    {
      provide: XPTO_TOKEN,
      useFactory: async () => {
        const xptoService = new XptoService()
        await xptoService.build()
        return xptoService
      },
    },
  ],
  exports: [XptoService],
})
export class XPTOModule {}
// My service
@Injectable()
export class XPTOService {
  private xptoSoapInstance: XPTOClient // This guy is being imported at the top of the file
  constructor() {}

  public async build() {
    this.xptoSoapInstance = new XPTOClient()
    await this.xptoSoapInstance.build() // <- this guy here is the lib that creates a new instance of the SOAP client
  }

  // rest of the service
// My service
@Controller('agency')
export class AgencyController {
  constructor(
    private agencyService: AgencyService,
    @Inject(XPTO_TOKEN) private xptoService: XPTOService,
  ) {}
#

I put that Service if the middle, instead of simply calling the lib, because I have to parse the data that comes from the Lib, but i'm not sure if this is the right approach

junior cove
#

so the service has some logic and then builds the soap instance, after that, anyone can use the XPTOClient?

craggy bridge
#

The service does the following:

  • Run the build method from the XPTOClient(which creates a new instance of soap), this build method should run at the start of the application
  • Has some methods that calls the XPTOClient(which now already have the soap instance), fetch the data, parse that data and returns
#

The XPTOClient is another package that i built for this specific case, which i mapped all SOAP methods available, just to let you know

#

And that service will be used in many other parts of my NestJS application

junior cove
#

Wouldn't it make more sense to push the build logic to the factory? That way, you can always import a ready client

craggy bridge
#

Could you give me an example?

#

Just a pseudo-code

junior cove
#
@Module({
  imports: [],
  providers: [
    {
      provide: XPTOClient,
      useFactory: async () => {
        const xptClient = new XPTOClient()
        /* do your logic */
        await xptClient.build()
        return xptoClient;
      },
    },
  ],
  exports: [XPTOClient],
})
export class XPTOModule {}
#

*where you can move the factory to its own thing so you do not pollute your module

craggy bridge
#

So in this case, i would import that XPTOClient into my service?

junior cove
#

Yes and it would already be in a consumable state

#

because the factory will handle it

#

You can also use nest hooks to do something when the module gets initialized, up to you

craggy bridge
#

Think i got it, let me test it here

#

Let me ask something, how should i import that XPTOClient into my Service? Using @Inject?

junior cove
#

up to you, if you dont care about directly injecting the client then you can just use it like so

#
class Service {
  public constructor(client: XTPOClient) {}
}
#

if you want to abstract it away, then the inject decorator with a custom token and an interface is an option

craggy bridge
#

Got it, i think that i'm making progress here. I applying the changes you suggested, everything is fine, but when i import my Service to another module, inside the providers:[MyService] and get an error complaining about the XPTOClient being exported by the XPTOModule

junior cove
#

Can you show me the error?

#

You probably want to import the module instead of adding the service again

craggy bridge
#

Fixed here, it was missing to import XPTOClient as a provider of the module that contains the service

#

No errors during the build, but when i call a method from the service, it seems that XPTOClient wasn't instantiated correctly

junior cove
#

You probably want to import the module instead

craggy bridge
#

I created a SoapModule , and added it to another Module called SharedModule, which i import it to AppModule to be available to some other methods

#

I have another module, called AgencyModule that has a controller that i'm using to test that Service

#

Should i import SharedModule into AgencyModule as well?

#

Or import SoapModule directly?

junior cove
#

eh t hats too much information for my brain to handle at this time, sounds like you just created a circular dependency

#

Is your shared module global?

craggy bridge
#

Sorry to bother you with all that information 🤷‍♂️

junior cove
#

if its not global, then its not going to pass it to the next child the way you think it would

#

you would have to explicitly import shared inside the module where its needed

craggy bridge
#

My shared module is just imported into my AppModule imports array

junior cove
#

Eh, give me a second I think I messed up

craggy bridge
#

I think i fixed it. I added a @Global decorator to my SharedModule,and on my AgencyModule its compiling without any errors

#

Now i'm getting an Axios error, which is probably not related to this

#

I'll think on how to fix it

#

I don't know if it is related, but my Service is being initialized by NestJS before my SoapModule which creates the SOAP instance inside the useFactory

#

My SoapModule is taking ~1s to initialize

junior cove
#

Okay so what I told you was correct, just re created it to make sure im not going insane. e.g. this works. I'm not a big fan of importing the module in shared and shared in Y and Z but alright

#

whats your next problem

craggy bridge
#

My Service,which uses that XPTOClient, relies on the useFactory inside my SoapModule

#

The module that has the service, gets initialized waay before my SoapModule(which is taking 1s)

#

So when i call the service methods, i'm getting an Axios error, which seems to be related to the SOAP client that wasn't initialized as expected

junior cove
#

Why is the service not in the same module though? Aren't they related?

craggy bridge
#

I thought they should be separated, according to your examples 🥲

#

Let me merge them

junior cove
#

Code that belongs together should be together ^^

#

just to give you a visual on what I have

#

anyway I hope this is enough to get you forward, Im desperate for sleep

#

had 4 hours last night and its already 00:40 here, cheers!

craggy bridge
#

Have a good night!

#

And thanks for all your help