In strictly typed object-oriented languages such as Java or C#, it's a common practice to invoke a method from a service by calling an interface as opposed to an actual service. This pattern is useful when swapping dependencies on the fly (like logging to the console or to the database) or unit testing, as we're not bounded to work with actual classes, but rather abstractions.
Nest.js however does not allow for using interfaces as providers because injection tokens only work strings, symbols and classes (as explained here). However, it does work with Abstract classes which we'll use here.
export default abstract class WebFramework {
abstract getHello(): string;
}
Each service extends the abstract class above.
import { Injectable } from '@nestjs/common';
import WebFramework from '../WebFramework.class';
@Injectable()
export class AngularService extends WebFramework {
getHello(): string {
return 'Hello Angular!';
}
}
import { Injectable } from '@nestjs/common';
import WebFramework from '../WebFramework.class';
@Injectable()
export class ReactService extends WebFramework {
getHello(): string {
return 'Hello React!';
}
}
In AppModule we'll inject our services using the abstract class as a provider.
import { ReactService } from './services/react/react.service';
import { AngularService } from './services/angular/angular.service';
import WebFramework from './services/WebFramework.class';
@Module({
imports: [],
controllers: [AppController, CoffeesController],
providers: [
AppService,
{
provide: WebFramework, // our custom injection token
useClass: React or Angular Service,
},
],
})
export class AppModule {}
Here we have to use @Inject() when importing dependencies as we're using a custom injection token.
import { Controller, Get, Inject } from '@nestjs/common';
import WebFramework from './services/WebFramework.class';
@Controller()
export class AppController {
constructor(
@Inject(WebFramework) private readonly webFramework: WebFramework,
) {}
@Get()
getHello(): string {
return this.webFramework.getHello();
}
}
The beauty here is that the consumer has no clue what is webFramework.getHello() service it is.
import { ReactService } from './services/react/react.service';
import { AngularService } from './services/angular/angular.service';
import WebFramework from './services/WebFramework.class';
@Module({
imports: [],
controllers: [AppController, CoffeesController],
providers: [
AppService,
{
provide: WebFramework, // our custom injection token
useClass: AngularService, // <--- Setting Angular Service!
},
],
})
export class AppModule {}
npm run start:dev
http:localhost:3000
> Hello Angular!
We just need to swap classes in AppModule
import { ReactService } from './services/react/react.service';
import { AngularService } from './services/angular/angular.service';
import WebFramework from './services/WebFramework.class';
@Module({
imports: [],
controllers: [AppController, CoffeesController],
providers: [
AppService,
{
provide: WebFramework,
useClass: ReactService, // <--- Setting React Service!
},
],
})
export class AppModule {}
http:localhost:3000
> Hello React!
And that's it!