In order to be able to inject different repositories, I have created a generic abstract class. This class implements a simple generic interface.
All repo classes for the app entities therefore should extend this abstract class and implements at least the generic Repo interface..
Entities are TypeOrm entities, nothing fancy here.
I need now to inject the entity repository in the service that access the data of this entity.
If I direclty inject the entity Repository class, that extends the abstract repo class, this is fine. No error and all is working well.
But If I want to inject the repository via a token and using the repo interface in the constructor of the service instead of the repo class itself, I got circular reference issue.
Below is the code.
The ProductsService
needs the ProductsRepository
to be injected.
This ProductsRepository
extends the abstract repo class
that itself extends a generic interface
Please note that when no error, in the ProductsService constructor
, the _repo constructor parameter is of type ProductsRepository
.
When circular error occurs, the type of the -repo is IProductsRepository and I'm injecting a token
.
I really do not understand where the circular ref is:
the interface of the abstract class repo and the abstract class
export interface IBaseRepository<T> {
findAll(options?: FindManyOptions<T>): Promise<T[]>;
}
export abstract class BaseAbstractRepository<T> implements IBaseRepository<T> {
private entity: Repository<T>;
protected constructor(entity: Repository<T>) {
this.entity = entity;
}
public async findAll(options?: FindManyOptions<T>): Promise<T[]> {
return await this.entity.find(options);
}
}
The ProductsRepository class that extends the abstract repo and implements the interface
export type IProductsRepository = IBaseRepository<ProductsEntity>;
@Injectable()
export class ProductsRepository extends BaseAbstractRepository<ProductsEntity> implements IProductsRepository {
constructor(@Inject(EDatabaseProviders.DATABASE_CONNECTION) readonly _dbConnection: DataSource) {
super(_dbConnection.getRepository<ProductsEntity>(ProductsEntity));
}
}
The providers of the ProductsModule
export enum EProductsProviders {
VALUE = "VALUE",
REPOSITORY = "REPOSITORY",
}
export const productsProviders: Array<Provider> = [
{
provide: EProductsProviders.VALUE,
useValue: 32,
},
{
provide: EProductsProviders.REPOSITORY,
useClass: ProductsRepository,
},
ProductsRepository,
ProductsService,
];
The ProductsService that doesn't trigger the circular error (please note the type of the repo private parameter in constructor)
@Injectable()
export class ProductsService {
constructor(private readonly _repo: ProductsRepository) {} //<--- no error when using the class
async getAll(): Promise<TProductsDtoOutput[]> {
const resu = await this._repo.findAll({
relations: {
transactions: true,
},
});
return resu.map((r) => {
const { id, ...rest } = r;
return rest as TProductsDtoOutput;
});
}
}
the ProductService that triggers the error of circular dependency
@Injectable()
export class ProductsService {
constructor(
@Inject(EProductsProviders.REPOSITORY)
private readonly _repo: IProductsRepository) {} // <--- error A circular dependency has been detected inside ProductsModule
async getAll(): Promise<TProductsDtoOutput[]> {
const resu = await this._repo.findAll({
relations: {
transactions: true,
},
});
return resu.map((r) => {
const { id, ...rest } = r;
return rest as TProductsDtoOutput;
});
}
finally the ProductsModule
@Module({
imports: [DatabaseModule, AuthenticationModule],
controllers: [ProductsController],
providers: [...productsProviders],
})
export class ProductsModule {}
Looking at your providers, this is expected. The issue here is that your ProductsService
imports a value from the products.providers
file, but the products.providers
file imports the products.service
file which is where eProductsService
is declared. To fix this, move your EProductsProviders
enum to a products.constants
file and have the products.service
and products.providers
import the enum from that file, thus eliminating the circular file import.