Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In NestJs, how to inject a service based on its interface?

I have the next module: payment.module.ts

@Module({
  controllers: [PaymentController],
})
export class PaymentModule {}

And in the next service I want to have access to a service based on an interface

payment.service.ts

export class PaymentService {
   constructor(private readonly notificationService: NotificationInterface,
}

notification.interface.ts

export interface NotificationInterface {
  // some method definitions
}

notification.service.ts

@Injectable()
export class NotificationService implements NotificationInterface {
  // some implemented methods
}

The question is how do I inject NotificationService based on NotificationInterface?

like image 304
Gabriel Vasile Avatar asked Sep 13 '25 15:09

Gabriel Vasile


2 Answers

This is the solution I found... using Interfaces as value types is not possible as they only exist during development. After transpiling interfaces no longer exist resulting in empty object values. There is a solution for your problem though using string keys as provide values and the inject decorator:

payment.module.ts

@Module({
  providers: [
    {
      provide: 'NotificationInterface',
      useClass: NotificationService
    }
  ]
})
export class PaymentModule {}

payment.service.ts

export class PaymentService {
   constructor(@Inject('NotificationInterface') private readonly notificationService: NotificationInterface,
}
like image 68
Gabriel Vasile Avatar answered Sep 15 '25 05:09

Gabriel Vasile


As was mentioned by Gabriel, you cannot use interfaces since they are not present at runtime, but you can use an abstract class. They are available at runtime, so they can be used as your dependency injection token.

In Typescript, class delcarations also create types, so you can also implement them, you don't have to extend them.

Following your example, you could do the following:

notification.interface.ts

export abstract class NotificationInterface {
  abstract send(): Promise<void>;
  // ... other method definitions
}

notification.service.ts

export class NotificationService implements NotificationInterface {
  async send() {...}
} 

Then in your module, provide it like so:

import NotificationInterface from "..."
import NotificationService from "..."

@Module({
  providers: [
    {
      provide: NotificationInterface,
      useClass: NotificationService
    }
  ]
})
export class PaymentModule {}

And finally, use the injected service via the interface in payments.service.ts

export class PaymentService {
   constructor(private readonly notificationService: NotificationInterface) {}
}

Now you don't need to provide any custom tokens (as strings or symbols) that are somewhat "unrelated" to your class implementation (and only a DI construct), but you can use the structure from your OOP model.

like image 37
Matthias S Avatar answered Sep 15 '25 05:09

Matthias S