Search code examples
typescriptdependency-injectionservicenestjsfactory

Using multiple files(factory and implementation) which depend on each other in nestjs module


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!


Solution

  • 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.