nestjs instantiate not injectable class - nestjs

i'm pretty new in nestJS, so you can address me directly to documentation if my question was covered there, but i can't figure out how to create instance of none-injectable class.
Here is simplified version of code
export default class BoardingPass {
constructor(
orderId: string,
companyIATa: string,
token: string,
private readonly checkinOrder = new CheckinOrder(orderId, companyIATa, token) // <-- i need to instantiate CheckinOrder
) {}
}
So i need to instantiate the CheckinOrder class but it requires ApolloClient which can be provided only via nestJS DI mechanism
export default class CheckinOrder implements ICheckinOrder {
private order: CheckinOrderObject;
constructor(
private readonly id: string,
private readonly carrierIATACode: string,
private readonly accessToken: string,
private readonly apolloClient: ApolloClient<NormalizedCacheObject> // can't figure out how to pass it via DI
) {}
}
update
Read about custom providers, it seems like it what i need, but as you can see there is some dynamic arguments passed to init method of CheckinOrder
const checkinOrderProvider: Provider<CheckinOrder> = {
useFactory: apolloClient => new CheckinOrder('1', '2', '3', apolloClient),
provide: CheckinOrder,
inject: [ApolloClient]
};
#Module({
imports: [ConfigModule.forRoot(), ApolloClientModule],
providers: [checkinOrderProvider],
exports: [checkinOrderProvider]
})
export class GooglePayModule {}
and only Apollo client is static, so its still unclear how to implement regular interface composition when class Boarding pass has instance of class CheckinOrder as property :(

You can provide the factory method and use it somewhere to create your service just in time. As we do here:
https://github.com/valueadd-poland/pimp-my-pr/blob/master/libs/server/repository/infrastructure/src/lib/repositories/repository-repository.adapter.ts#L18

Related

Nest.js inject service into plain typescript class

I have a NestJS App and I have a Class where the app generates multiple instances from. Inside that class I need to access a service method but I dont know how to inject a service into a plain class.
This is the service I want to use inside the class "Stream"
import { Injectable } from '#nestjs/common';
import * as technicalindicators from 'technicalindicators';
import { CandleIndex } from 'src/utils/CandleIndex';
import * as ccxt from 'ccxt';
#Injectable()
export class AnalysisService {
async RSI(ohlcv: ccxt.OHLCV[], period: number) {
const close = ohlcv.map((candle) => candle[CandleIndex.CLOSE]);
const result = technicalindicators.rsi({ values: close, period: period });
return result;
}
}
import { AnalysisService } from './analysis.service';
import { Module } from '#nestjs/common';
#Module({
imports: [],
controllers: [],
providers: [AnalysisService],
exports: [AnalysisService],
})
export class AnalysisModule {}
Here is the class I want to get access to the analysis service. The class is a normal typescript class, its not part of any module.
export class Stream {
private exchange: ccxt.Exchange;
private market: ccxt.Market;
private timeframe: string;
private ohlcv_cache: ccxt.OHLCV[];
private createdAt: Date;
private stream;
#Inject(AnalysisService)
private readonly analysisService: AnalysisService;
constructor(exchange: ccxt.Exchange, market: ccxt.Market, timeframe: string) {
this.exchange = exchange;
this.market = market;
this.timeframe = timeframe;
this.createdAt = new Date();
this.initialize();
console.log(this.analysisService);
}
}
When I do this and call a method of the analysis service inside of the Stream class, analysisService is undefined.
As you're calling new Stream() yourself, Nest will do no injection for you. You'd need to either pass the AnalysisService instance yourself, or you'd need to create a setter for that property before running any methods that need the AnalysisService.

NestJS onModuleInit not working when specific service is added to constructor

I have a bunch of services defined in my NestJS project. Some of the services are used as common services in a lot of other services. So they are imported in a few modules as well. But I noticed that when specific service srvD is imported in another service srvE2, the onModuleInit is not being called when the project starts.
The project starts without any error. Not really sure what's happening.
An example of the project structure. Issue is in srvE2
srvA.ts
#Injectable()
export class SrvA {
constructor(
private somePkgSrv: SomePkgSrv,
) {}
}
srvB.ts
#Injectable()
export class SrvB {
constructor(
private srvA: SrvA,
) {}
}
srvC.ts
#Injectable()
export class SrvC {
constructor(
private srvA: SrvA,
private srvB: SrvB,
) {}
}
srvD.ts
#Injectable()
export class SrvD {
constructor(
private srvA: SrvA,
private srvB: SrvB,
private srvC: SrvC,
) {}
}
srvD.module.ts
#Module({
providers: [SrvA, SrvB, SrvC, SrvD],
exports: [SrvD],
})
srvE1.ts
export class SrvE1 implements OnModuleInit {
constructor(
private srvA: SrvA,
private srvB: SrvB,
private srvC: SrvC,
) {}
async onModuleInit() {
console.log ('I can print! Yay!')
}
}
srvE2.ts
export class SrvE2 implements OnModuleInit {
constructor(
private srvA: SrvA,
private srvB: SrvB,
private srvC: SrvC,
private srvD: SrvD,
) {}
async onModuleInit() {
console.log ('I refuse to print so long as SrvD is here. Comment it and I will
print')
}
}
srvE.module.ts
#Module({
import: [SrvD], // the module
providers: [SrvE1, SrvE2], // the services
exports: [SrvE1, SrvE2],
})
I am dealing with a similar issue. Based on the comments here I tried changing the scoping for my new dependency from Scope.REQUEST to Scope.DEFAULT and that resolved my issue.
Request scoped providers spread this scope to whole chain of injection. Consequently, if you use it, even deeply inside your code, then onModuleInit won't run.
That being said, well ... request scope isn't very recommended, performance wise. Here is an interesting article : Why You Should Avoid Using Request-scoped Injection in NestJS
I've also bumped into this question & its answers : How to inject a request scoped provider at NestJS controller?
I've tried nj-request-scope package from #profes, and it works fine, especially to fix your problem.
If you don't want to use it, you can still try to move your onModuleInit code in a default scoped service.

Nestjs: calling service functions from Model / Entity with sequelize hooks

In NestJS, I have to use a module service into an entity/model to populate data into elastic-search index. populating elastic search index logic is written in Job.service.ts.
I want to call that onCreate method from Job.service.ts from sequelize hooks present in models.
Here is code for Job.ts model/entity -
import { Table, Model, Column, AutoIncrement, PrimaryKey } from "sequelize-typescript";
#Table({ schema: "job", tableName: "job" })
export class Job extends Model<Job> {
#AutoIncrement
#PrimaryKey
#Column
id: number;
#Column
title: string;
#AfterCreate
static async jobAfterCreate(instance, options) {
// <--- need to call job service onCreate method here
}
#AfterUpdate
static async jobAfterUpdate() {}
#AfterDestroy
static async jobAfterDestroy() {}
}
and here is code for Job.service.ts -
//imports not added
#Injectable()
export class JobService {
constructor(
#Inject("SEQUELIZE")
private readonly sequelizeInstance: Sequelize,
#Inject(forwardRef(() => ElasticsearchService))
private readonly elasticsearchService: ElasticsearchService,
#InjectModel(Job)
private jobModel: typeof Job
) {}
// here will write logic for updating elastic search index
async onCreate(instance, options){
console.log("ON CREATE INSTANCE:", instance);
console.log("ON CREATE OPTIONS:", options);
}
async onDestroy(instance, options){
console.log("ON DESTROY INSTANCE:", instance);
console.log("ON DESTROY OPTIONS:", options);
}
}
I tried injecting service into Job model but it did not worked.
And I cannot write elastic search logic inside model directly because for that I need ElasticsearchService.
The Solution is To Override the provider
The primary way to inject information into the models is by overriding the injection behavior.
First, you would need to add a static property referencing the service in your model.
I am going to use the event emitter as an example here.
Your Model Class
import {Model, Table, Column, AfterCreate} from "sequelize-typescript";
import { EventEmitter2 } from "#nestjs/event-emitter";
#Table()
export class SomeModel extends <SomeModel> {
// this would be your referencing
public static EventEmitter: EventEmitter2;
#Column
public someColumn: string;
#AfterCreate
public static triggerSomeEvent(instance: SomeModel) {
SomeModel.EventEmitter.emit('YourEvent', instance);
}
}
The module where you are going to use the model
Now we are overriding the default injection process.
import { EntitiesMetadataStorage } from '#nestjs/sequelize/dist/entities-metadata.storage';
import {
getConnectionToken,
getModelToken,
SequelizeModule,
} from '#nestjs/sequelize';
import { EventEmitter2 } from '#nestjs/event-emitter';
// The provider override
const modelInjector: Provider = {
provide: getModelToken(AccountabilityPartnerModel, DEFAULT_CONNECTION_NAME),
useFactory: (connection: Sequelize, eventEmitter: EventEmitter2) => {
SomeModel.EventEmitter = eventEmitter;
if (!connection.repositoryMode) {
return SomeModel;
}
return connection.getRepository(SomeModelas any);
},
inject: [getConnectionToken(DEFAULT_CONNECTION_NAME), EventEmitter2],
};
// Updating the meta information of sequelize-typescript package to handle connection injection in to the model overridden.
EntitiesMetadataStorage.addEntitiesByConnection(DEFAULT_CONNECTION_NAME, [
SomeModel,
]);
// our custom module being used rather than the Sequelize.forFeature([SomeModel])
const someModelModule: DynamicModule = {
module: SequelizeModule,
providers: [modelInjector],
exports: [modelInjector],
};
#Module({
imports: [someModelModule],
providers: [SomeService],
})
export class SomeModule {
}
Inject your model into your service as you would do using Sequlize.forFeature and InjectModel indicated as below.
#Injectable()
export class SomeService {
constructor(#InjectModel(SomeModel) someModel: typeof SomeModel) {}
public someFunction(data: any) {
this.someModel.EventEmitter.emit('YourEvent', data);
}
}

Dependencies when testing in NestJS

As the first example in NestJS shows(https://docs.nestjs.com/fundamentals/testing), the dependency for CatsController is CatsService, so they make new instance of CatService and pass it in the params for CatsController, As easy as pie.
In my real world usage the service is the main service of the system, it depends on a multiple services which they depend on other services and almost all of them including the main one depend on injecting repositories of TypeORM.
How should I handle such of a complex dependency injection when testing?
An example:
Class MainService {
constructor(
#InjectRepository(SomeEntity1)
private readonly someRepo1: Repository<SomeEntity1>,
#InjectRepository(SomeEntity2)
private readonly someRepo2: Repository<SomeEntity2>,
#InjectRepository(SomeEntity3)
private readonly someRepo2: Repository<SomeEntity3>,
private readonly someService1: SomeService1,
private readonly someService2: SomeService2,
private readonly someService3: SomeService3,
private readonly someService4: SomeService4,
private readonly someService5: SomeService5,
private readonly someService6: SomeService6,
private readonly someService7: SomeService7,
private readonly someService8: SomeService8,
private readonly someService9: SomeService9
){}
}
Class SomeService1 {
constructor(
#InjectRepository(SomeEntity1)
private readonly someRepo4: Repository<SomeEntity4>,
#InjectRepository(SomeEntity2)
private readonly someRepo5: Repository<SomeEntity5>,
#InjectRepository(SomeEntity3)
private readonly someRepo6: Repository<SomeEntity6>,
private readonly someService4: SomeService4,
private readonly someService5: SomeService10,
private readonly someService9: SomeService11
){}
}
I could find a solution online.
Thanks!
Generally when you do unit testing, you want to mock the dependencies, because you want to isolate the unit under test from the rest of your application.
Nest offer multiple ways to mock and Jest himself also offers other possibilities (https://jestjs.io/docs/es6-class-mocks).
One way is to use custom providers and provide your own mock:
const module = await Test.createTestingModule({
providers: [
MainService,
{
provide: SomeService1
useValue: MockSomeService1
}
]
})
.compile()
MockSomeService1 can be an object, a class or a factory function. Refers to the documentation for more details: https://docs.nestjs.com/fundamentals/custom-providers
If you have a lot of dependencies, that could be a good use case for using auto mocking and https://www.npmjs.com/package/#golevelup/ts-jest combined.
import { createMock } from '#golevelup/ts-jest'
const module = await Test.createTestingModule({
providers: [
MainService
]
})
.useMocker(() => createMock())
.compile()
More details on auto mocking: https://docs.nestjs.com/fundamentals/testing#auto-mocking

Access the collection of providers for a module in nestjs

Is there any way for a method in a class decorated with #Module() to iterate over all provider objects defined for that module?
You can access metadata by a key and module class.
For example:
console.log(Reflect.getMetadata('providers', UserModule));
Output will be like:
[class UserService], [class ConfigService] ]
Underhood, #Module decorator looks like this:
function Module(metadata) {
const propsKeys = Object.keys(metadata);
validate_module_keys_util_1.validateModuleKeys(propsKeys);
return (target) => {
for (const property in metadata) {
if (metadata.hasOwnProperty(property)) {
Reflect.defineMetadata(property, metadata[property], target);
}
}
};
}
It defines a metadata by a property key (providers, controllers, imports and etc.) and saves it for a target (module class).
Metadata API: https://github.com/rbuckton/reflect-metadata#api
This might not be the best solution, but at least it 'works for me'. I ended up defining all the provider classes in an array:
const MyProviders = [ ProviderClass1, ProviderClass2, ...];
Then used that constant in the module definition:
#Module({
imports: [],
providers: MyProviders,
exports: MyProviders,
controllers: []
})
export class MyModule {
I defined the constructor to include a ModuleRef:
public constructor(private readonly moduleRef: ModuleRef) {}
And then it was a relatively simple method to iterate over the instantiated provider objects:
public someMethod(): void {
for (const cls of MyProviders) {
const provider = this.moduleRef.get(cls.name);
//
// Can now call methods, etc on provider
//
}
}
If there's a better way of doing this, I'd love to hear it.

Resources