I'm trying to implement a service factory in my nestjs module and have a dependency problem.
Everything is in one module(TaskModule) and I have three different classes inside that should be available to use inside the module(TaskService, TaskFactory, SendQueuedEmailsTask). My guess the problem is that one of the files(TaskFactory) depends on other Injectable file in the module(SendQueuedEmailsTask)? The error I'm getting is:
Nest can't resolve dependencies of the TaskFactory (?). Please make sure that the argument SendQueuedEmailsTask at index [0] is available in the TaskFactory context.
Potential solutions:
- If SendQueuedEmailsTask is a provider, is it part of the current TaskFactory?
- If SendQueuedEmailsTask is exported from a separate @Module, is that module imported within TaskFactory?
@Module({
imports: [ /* the Module containing SendQueuedEmailsTask */ ]
})
Here is the code:
TaskFactory file
@Injectable()
export class TaskFactory {
constructor(private readonly sendQueuedEmailsTask: SendQueuedEmailsTask) {}
createTask(type: TaskType) {
if(type === 'sendQueuedEmailsTask') return this.sendQueuedEmailsTask;
}
}
SendQueuedEmailsTask Implementation:
@Injectable()
export class SendQueuedEmailsTask {
readonly actionName = 'sendQueueEmail';
process() {
// task implementation details
}
}
and finally how it will be used in the service, TaskService:
@Injectable()
export class TaskService {
constructor(
private readonly taskFactory: TaskFactory,
) {}
execute(name: string){
// using TaskFactory
const Task = this.taskFactory.createTask(name);
Task.process();
}
}
Problem is somewhere in my TaskModule file I think:
@Module({
imports: [
SendQueuedEmailsTask,
TaskFactory,
],
providers: [TaskService],
controllers: [TaskController],
exports: [TaskService],
})
export class TaskModule {}
Have already tried to put SendQueuedEmailsTask and TaskFactory in providers array in the module but the message stays the same. Don't know what I'm doing wrong, maybe that's not the way to implement factory class while using DI? any help is appreciated really!
As you stated, SendQueuedEmailsTask
and TaskFactory
should be moved into the providers
and removed from the imports
.
imports
are used to import services exported from other modules only (related doc), so SendQueuedEmailsTask
and TaskFactory
do not belong there.
To be sure to have Nestjs find the service you want to inject, you can create your own injection tokens in your TokenModule
:
// injection-tokens.ts
export enum InjectionTokens {
SEND_QUEUED_EMAILS_TASK = 'TASK_MODULE/SEND_QUEUED_EMAILS_TASK',
TASK_FACTORY = 'TASK_MODULE/TASK_FACTORY',
TASK_SERVICE = 'TASK_MODULE/TASK_SERVICE',
}
Then create the providers:
// Create providers.
@Module({
providers: [
{ provide: InjectionTokens.SEND_QUEUED_EMAILS_TASK, useClass: SendQueuedEmailsTask },
{ provide: InjectionTokens.TASK_FACTORY, useClass: TaskFactory },
{ provide: InjectionTokens.TASK_SERVICE, useClass: TaskService },
],
controllers: [TaskController],
exports: [
{ provide: InjectionTokens.TASK_SERVICE, useClass: TaskService },
],
})
export class TaskModule {}
And inject the services in your classes:
// Inject services.
@Injectable()
export class TaskFactory {
constructor(@Inject(InjectionTokens.SEND_QUEUED_EMAILS_TASK) private readonly sendQueuedEmailsTask: SendQueuedEmailsTask) {}
// ...
}
@Injectable()
export class TaskService {
constructor(@Inject(InjectionTokens.TASK_FACTORY) private readonly taskFactory: TaskFactory) {}
// ...
}
It's more verbose but the advantage is you have a better control on injection and avoid service name collision across different module.