in angular, Circular dependency when using providedIn and forRoot - javascript

I am developing an angular library. it has an internal service. which is defined like below.
Used providedIn to be tree-shakable. and didn't use providedIn:'root' because its internal and just used in the module scope.
#Injectable({
providedIn: MyChartModule,
})
export class LineChartLibService {
and when i wanted to add forRoot in module definition to have just one instance in lazy loading, it encounter Circular dependency.
export class MyChartModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: MyChartModule,
providers: [LineChartLibService],
};
}
}
what should we do in this situations?
is it possible to have a lazy load-able tree-shakable service in library?
WARNING: Circular dependency: dist\my-chart\esm2015\lib\my-chart.module.js -> dist\my-chart\esm2015\lib\line-chart\line-chart.component.js
-> dist\my-chart\esm2015\lib\line-chart-lib.service.js -> dist\my-chart\esm2015\lib\my-chart.module.js

I have previously answered a (non-duplicate) question that provides you with alternatives to this approach.
https://stackoverflow.com/a/60377431/5367916
Take this setup:
my-module.ts
declarations: [
MyComponent
]
my-service.ts
#Injectable({ providedIn: MyModule })
my-component.ts
constructor(private myService: MyService) {}
my-service imports my-module
my-module imports my-component
my-component imports my-service
There is a circular dependency.
A workaround
The workaround for this is to create a service module and import that into your module.
my-module.ts
imports: [
MyModuleServices
],
declarations: [
MyComponent
]
my-module-services.ts
// no imports or declarations
my-service.ts
#Injectable({ providedIn: MyModuleServices })
my-component.ts
constructor(private myService: MyService) {}
Alternative
The much more straightforward way is to add the service to your module's providers.
#NgModule({
providers: [ MyService ]
})
export class MyModule {}

I think what happens is the following:
You have the line-chart.component.js that probably calls for the LineChartLibService.
Within the LineChartLibService you say that this Injectable is provided in MyChartModule.
Within the MyChartModule you probably declare the line-chart.component.js to be part of this module.
This means the module asks for the component, which asks for the service, which asks for the module which then asks for the component again: A circular dependency.
If you add more code and context to your question, we might be able to make a better suggestion ;-)

Related

Why won't my Service inject properly into a component in Angular?

The structure of my Angular App (and relevant portions) is as follows:
app/app.module.ts
import { DbService } from './db.service'
const APP_CONTAINERS = [
DefaultLayoutComponent,
DefaultHeaderComponent,
];
#NgModule({
declarations: [ ...APP_CONTAINERS ],
providers: [ DbService ]
})
export class AppModule {}
app/containers/default-layout.component.ts
import { Component, HostBinding, Inject } from '#angular/core';
import { DbService } from '../../db.service';
import { navItems } from './_nav';
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
#Component({
selector: 'app-dashboard',
templateUrl: './default-layout.component.html'
})
export class DefaultLayoutComponent {
#HostBinding('class.c-app') cAppClass = true;
constructor(private DbService: DbService) {}
async ngOnInit() {
console.log('working')
}
}
I use this same service successfully in other portions of my site but they all have specific module.ts files. I followed the Angular tutorial to get to where I am but I am still getting errors no matter how I try to inject this service.
I also tried using #Inject in my constructor but received the same error.
I currently receive the following error in my console:
ERROR Error: Uncaught (in promise): NullInjectorError: R3InjectorError(e)[e -> e -> e -> e]:
NullInjectorError: No provider for e!
NullInjectorError: R3InjectorError(e)[e -> e -> e -> e]:
NullInjectorError: No provider for e!
I use this same service successfully in other portions of my site but they all have specific module.ts files
So it is unclear how you divide your modules. It doesn't look from the module file like that component is in the module that provides the service. The service has to be in the providers for the injection tree where the component resides. If that module file is for a different module, then the injector has to way to know what you want provided for the service.
One solution would be to add it in the providers for your component. That would get a different instance of the service than in your other modules however:
#Component({
selector: 'app-dashboard',
templateUrl: './default-layout.component.html',
providers: [DbService]
})
export class DefaultLayoutComponent {
You could also add it to the providers for the module your component resides in, but that would get a different instance as well.
I think the best way would be to define the service so that it should be provided in the root module. This way you don't have to add it in the providers anywhere, angular will automatically add it to the root module's providers when it is required. That leads to what Octavian was saying in his comment. I think you just put this code incorrectly in the component instead of the service. Move this to the service and remove the service from the providers arrays in your module(s):
#Injectable({
providedIn: 'root'
})

NestJS - Inject factory provider into another provider doesn't work

I'm trying to use nest custom provider with factory, for some reason I'm struggling to get it working.
I've created the following sample -
interface ProviderOptions: {x:string, y:number};
interface AsyncProps {useFactory:(...args:any[])=> ProviderOptions, inject:any[], imports:any[] }
#Module({})
export class MyModule {
static forRootAsync(asyncProps: AsyncProps): DynamicModule {
const myFactory: Provider = {
provide: "MY_PROVIDER",
useFactory: asyncProps.useFactory,
inject: asyncProps.inject,
};
return {
module: MyModule,
providers: [myFactory, MyService],
exports: [MyService],
imports: [...asyncProps.imports],
};
}
}
#Injectable()
export class MyService {
constructor(#Inject("MY_PROVIDER") this options:ProviderOptions){
}
}
For some reason I'm unable to resolve MyService -
Error: Nest can't resolve dependencies of the MyService (?). Please
make sure that the argument dependency at index [0] is available in
the MyModule context.
what I'm missing here?
Thanks!
UPDATE -
so now it's really strange -
originally MyService was in a different file, next to MyModule. when moved MyService to the same file as MyModule, the above code does work as expected. how can it be?
Based on your error, you have a circular file import which means that typecript can't resolve the class name and in turn Nest can't create the class. I just answered another question on it here

How to inject a nestjs service into another service when both belong to the same module?

I have the following scenario in NestJS:
// user.service.ts
#Injectable()
export class UserService {
constructor(
private readonly userRepository: UserRepository,
private readonly userProfileService: UserProfileService,
) {}
}
// user-profile.service.ts
#Injectable()
export class UserProfileService {
constructor(
private readonly userProfileRepository: UserProfileRepository,
) {}
}
// user.module.ts
#Module({
imports: [DataRepositoryModule], // Required for the repository dependencies
providers: [UserService, UserProfileService],
exports: [UserService, UserProfileService],
})
export class UserModule {}
However, when I try to use the UserService inside a controller from another module, I get the following error:
Nest can't resolve dependencies of the UserService (UserRepository, ?). Please make sure that the argument dependency at index [1] is available in the UserModule context.
Potential solutions:
- If dependency is a provider, is it part of the current UserModule?
- If dependency is exported from a separate #Module, is that module imported within UserModule?
#Module({
imports: [ /* the Module containing dependency */ ]
})
Controller code:
#Controller()
export class UserController {
constructor(private readonly userService: UserService) {}
}
Controller module:
#Module({
imports: [],
providers: [],
controllers: [UserController],
})
export class UserManagementModule {}
Main app.module.ts:
#Module({
imports: [
UserManagementModule,
UserModule,
DataRepositoryModule.forRoot(),
],
controllers: [],
providers: [],
})
export class AppModule {}
I'm confused because I'm doing exactly what the error suggests, adding both services inside the providers array (UserModule). What could I be missing?
From your error, it looks like there's a circular dependency between your file imports. Check to make sure that you don't have a circular import chain going on (i.e. ServiceA imports ServiceB imports ServiceA or ServiceA imports ModuleA imports ServiceB imports ServiceA). This can especially become common common when using barrel files inside the same module.

Tree shaking for libraries in Angular 8

I'm planning on making a standalone Angular service for Angular 8+, and I've been reading up on how to make it tree tree-shakable.
I believe all we need to do is this:
#Injectable({
providedIn: 'root',
useFactory: () => new Service('dependency'),
})
export class Service {
constructor(private dep: string) {
}
}
As this tells Angular how to construct the service.
Is this all we need to do? I've seen other NPM modules like this one use ModuleWithProviders like this:
export class Module {
static forRoot(): ModuleWithProviders {
return {
ngModule: Module,
providers: [
StripeScriptTag
],
}
}
I assume that this is not necessary because the providers array still does not describe how to instantiate the service.
All you need is the
providedIn: 'root'
Services that are provided in root are tree shakeable. If it is provided in the root then you get the same singleton instance in the entire application. You only need to add it to a providers array if you want a new instance of the service across your module instead of the global singleton instance.

Inject nestjs service from another module

I've got a PlayersModule and an ItemsModule.
I want to use the ItemsService in the PlayersService.
When I add it by injection:
import { Injectable } from '#nestjs/common';
import { InjectModel } from 'nestjs-typegoose';
import { ModelType, Ref } from 'typegoose';
import { Player } from './player.model';
import { Item } from '../items/item.model';
import { ItemsService } from '../items/items.service';
#Injectable()
export class PlayersService {
constructor(
#InjectModel(Player) private readonly playerModel: ModelType<Player>,
private readonly itemsService: ItemsService){}
I get this nest error :
[Nest] 11592 - 2018-8-13 11:42:17 [ExceptionHandler] Nest can't
resolve dependencies of the PlayersService (+, ?). Please make sure
that the argument at index [1] is available in the current context.
Both modules are imported in the app.module.ts. Both services are working alone in their module.
You have to export the ItemsService in the module that provides it:
#Module({
controllers: [ItemsController],
providers: [ItemsService],
exports: [ItemsService]
^^^^^^^^^^^^^^^^^^^^^^^
})
export class ItemsModule {}
and then import the exporting module in the module that uses the service:
#Module({
controllers: [PlayersController],
providers: [PlayersService],
imports: [ItemsModule]
^^^^^^^^^^^^^^^^^^^^^^
})
export class PlayersModule {}
⚠️ Don't add the same provider to multiple modules. Export the provider, import the module. ⚠️
Let' say you want to use AuthService from AuthModule in my TaskModule's controller
for that, you need to export authService from AuthModule
#Module({
imports: [
....
],
providers: [AuthService],
controllers: [AuthController],
exports:[AuthService]
})
export class AuthModule {}
then in TaskModule, you need to import AuthModule (note: import AuthModule not the AuthService in TaskModule)
#Module({
imports:[
AuthModule
],
controllers: [TasksController],
providers: [TasksService]
})
export class TasksModule {}
Now you should be able to use DI in TaskController
#Controller('tasks')
export class TasksController {
constructor(private authService: AuthService) {}
...
}
The question is answered by Kim Kern. But I just want to remind people who read through this comment. Whenever you get this error, you should follow these steps that may help you easily figure out where the stuck is:
Make sure the Module which provides providers was imported.
Make sure the provider which you are using is exported.
For example, you have category module which contains category service, post module has post service and it has category service as a dependency:
#Module({
controllers: [CategoryController],
providers: [CategoryService],
exports: [CategoryService] // Remember to export
})
export class CategoryModule {}
And
#Module({
imports: [CategoryModule], // Make sure you imported the module you are using
controllers: [PostController],
providers: [PostService]
})
export class PostModule {}
Don't forget to use this annotation.
Nest uses this to detect singleton class.
In spring boot - Java, this one used to be called Bean. Read more:
#Injectable()
export class PostService {
constructor(private readonly categoryService: CategoryService // This will be auto injected by Nestjs Injector) {}
}
I solved my problem by removing #Inject() from the argument in my constructor that was passing the exported service.
I believe that you faced the same problem i had. My scenario was 2 sibling custom modules (user, auth) that needed to use each other's services. I used circular DI to solve it. please check this link
Let me know whether if it solved your issue, maybe I can advise you further.
Solved my problem by changing the way of importing the constant string (TOKEN) used in #Inject()) of my provider... be careful using index.ts whith export * from module.ts, nest won't resolve the dependecy
Based on the answer by Kim Kern nowadays we should add only injected service into our service without any decorators (#Inject() doesn't required). After that it will work right. That was my mistake and probably can help others.
Steps 1. Export the file that you want
Step 2. Import the whole module.
I initially made a mistake of adding the file as provider and also adding the module which was throwing error.

Categories

Resources