I have an issue with NestJS and it appears to be for just 1 module and all the others work fine. I have the following modules.
The error is:
[ExceptionHandler] Nest can't resolve dependencies of the ApplicationService (ApplicationModel, AwsService, UserService, ?, JobService). Please make sure that the argument at index [3] is available in the ApplicationModule context.
The AgencyService is [3]. If I remove the AgencyModule from the ApplicationModule NestJS successfully compiles and I can make API calls.
AgencyModule,
ApplicationModule,
AuthModule,
JobModule,
UserModule,
All these modules are required by other modules for their service providers so rather than import them between each other using forwardRef() I just made them Global() - May not be best practice but hey ho (it works).
My AppModule file.
#Module({
imports: [
MongooseModule.forRootAsync({
useFactory: (configService: ConfigService) => ({
uri: configService.get('MONGO_DB_URL'),
useNewUrlParser: true,
}),
imports: [ConfigModule],
inject: [ConfigService],
}),
ConfigModule,
AgencyModule,
ApplicationModule,
AuthModule,
DevModule,
JobModule,
UserModule,
VideoModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Each module folder has the following structure.
agency/
- dto/
- agency.controller.ts
- agency.interface.ts
- agency.schema.ts
- agency.service.ts
- agency.module.ts
My AgencyModule file.
#Global()
#Module({
imports: [
SharedModule,
MongooseModule.forFeature([{ name: 'Agency', schema: AgencySchema }]),
],
controllers: [
AgencyController,
],
providers: [
AgencyService,
AwsService,
],
exports: [
AgencyService,
],
})
export class AgencyModule implements NestModule {
public configure(consumer: MiddlewareConsumer) {
consumer
.apply()
.forRoutes(
{ path: 'agency', method: RequestMethod.GET },
);
}
}
My AgencyService file.
#Injectable()
export class AgencyService {
constructor(
#InjectModel('Agency') private readonly agencyModel: Model<Agency>,
private readonly awsService: AwsService,
private readonly applicationService: ApplicationService,
) {
//
}
// More stuff here but not worth adding to the snippet.
}
My ApplicationModule file.
#Global()
#Module({
imports: [
SharedModule,
MongooseModule.forFeature([{ name: 'Application', schema: ApplicationSchema }]),
],
controllers: [
ApplicationController,
],
providers: [
ApplicationService,
AwsService,
],
exports: [
ApplicationService,
],
})
export class ApplicationModule implements NestModule {
public configure(consumer: MiddlewareConsumer) {
consumer
.apply()
.forRoutes(
{ path: 'application', method: RequestMethod.GET },
);
}
}
My ApplicationService file.
#Injectable()
export class ApplicationService {
constructor(
#InjectModel('Application') private readonly applicationModel: Model<Application>,
private readonly awsService: AwsService,
private readonly userService: UserService,
private readonly agencyService: AgencyService,
private readonly jobService: JobService,
) {
//
}
// More stuff here but not worth adding to the snippet.
}
The AwsService is a shared service without a module.
Using #Global() does not automatically resolve circular dependencies, you still have to use #Inject(forwardRef(() => MyService)) on both sides, see the docs.
As you noted yourself, circular dependencies (forwardRef) and global modules (#Global) are bad style and should be avoided. Rather make your dependencies explicit. If you encounter a circular dependency extract the commonly used parts in a shared service/module that is imported by both sides, see this thread.
Related
I get this error
ERROR [ExceptionHandler] Nest can't resolve dependencies of the ItemController (ItemService, ?). Please make sure that the argument ImageFileS
service at index [1] is available in the ItemModule context.
I have this code
ImageFileModule :
#Module({
imports: [TypeOrmModule.forFeature([ImageFile]), ConfigModule, ItemModule],
controllers: [ImageFileController],
providers: [ImageFileService],
exports: [ImageFileService],
})
export class ImageFileModule {}
ItemModule :
#Module({
imports: [TypeOrmModule.forFeature([Item])],
controllers: [ItemController],
providers: [ItemService],
exports: [ItemService],
})
export class ItemModule {}
Service That I am trying to use:
#Injectable()
export class ImageFileService {
constructor(
#InjectRepository(ImageFile)
private readonly imageFileRepository: Repository<ImageFile>,
private readonly configService: ConfigService,
private readonly itemService: ItemService,
) {}
I'm trying to export and import a service in NestJS. It seems easy and I thought it should work like this but I got an error saying that Nest can't resolve the dependencies.
SettingsModule
This module has the service that should be imported, and exports it.
#Module({
imports: [
MongooseModule.forFeature([{ name: Setting.name, schema: SettingSchema }]),
],
providers: [SettingsService],
exports: [SettingsService],
})
export class SettingsModule {}
MsgraphModule
This module should import the service through the module because the service is injected in their service.
#Module({
imports: [SettingsModule],
providers: [MsgraphService],
})
export class MsgraphModule {}
AppModule
#Module({
imports: [
MongooseModule.forRoot('mongodb://localhost/lead-import', {
useCreateIndex: true,
}),
MsgraphModule,
SettingsModule,
...
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
What am I doing wrong here?
The problem was that I used the #Inject() decorator which is only needed for custom dependency injections.
#Injectable()
export class MsgraphService {
private client: Client;
private authenticator;
constructor(#Inject() private settingsService: SettingsService) {
this.init();
this.authenticator = new MSGraphAuthenticator();
}
...
}
So removing the #Inject() did the trick.
I've developed a library with shared components in Angular and I want to pass configuration there.
Everything is basically the same as in:
Pass config data using forRoot
Problem is, that I need to pass user to this module, which is fetched on start of application and saved in Redux state.
Is it possible to pass observable with user using forRoot while importing module? Maybe it's possible to pass something to this module 'later' with some lazy loading?
#select() private user$: Observable<User>
#NgModule({
imports: [
LibModule.forRoot({
user: user$
})
...
})
I've made it another way - by injecting my implementation of abstract service for getting settings. Code below:
Lib module declaration:
export interface LibSettings {
user: User;
url: string;
}
export abstract class AbstractLibSettingsService {
abstract getSettings(): LibSettings;
}
#NgModule({
declarations: [...],
imports: [...],
exports: [...]
})
export class LibModule {
static forRoot(implementationProvider: Provider): ModuleWithProviders {
return {
ngModule: LibModule,
providers: [
implementationProvider
]
}
}
}
Lib service, where I needed the user:
constructor(private settingsService: AbstractGlassLibSettingsService) {
}
In application that uses the lib, module declaration with import:
export const LIB_SETTINGS_PROVIDER: ClassProvider = {
provide: AbstractLibSettingsService,
useClass: LibSettingsService
};
#NgModule({
imports: [...
LibModule.forRoot(LIB_SETTINGS_PROVIDER)
],
...
})
Finally, the implementation of the service in application:
#Injectable()
export class LibSettingsService extends AbstractLibSettingsService {
#select() private user$: Observable<User>;
private user: User;
constructor() {
super();
this.user$.subscribe(user => this.user = user);
}
public getSettings(): GlassLibSettings {
...
}
I'm trying to inject a provider inside a guard that is wrapped in a decorator, but Nest is not being able to resolve dependencies, giving me the next error:
[ExceptionHandler] Nest can't resolve dependencies of the SecuredGuard (Reflector, ?). Please make sure that the argument at index [1] is available in the SecuredGuard context.
The main purpose of my approach is to avoid using two separate decorators like this:
#Controller()
export class AppController {
#Get()
#Secured('admin')
#UseGuards(SecuredGuard)
getHello(): string {
return this.appService.getHello();
}
}
And instead insert the #UseGuards(SecuredGuard) inside my custom decorator #Secured('admin') so it ends up like this:
#Controller()
export class AppController {
#Get()
#Secured('admin')
getHello(): string {
return this.appService.getHello();
}
}
This is how I'm implementing my custom decorator:
export function Secured(...roles: string[]){
const setMetadata = ReflectMetadata('roles', roles)
const setupGuard = UseGuards(SecuredGuard)
return (target: any, key?: string, descriptor?: any) => {
setMetadata(target, key, descriptor);
setupGuard(target, key, descriptor);
}
}
And this is my SecuredGuard, the AuthService is the dependency that couldn't be resolved:
#Injectable()
export class SecuredGuard implements CanActivate {
constructor(
private readonly _reflector: Reflector,
private readonly _authService: AuthService
) { }
async canActivate(context: ExecutionContext): Promise<boolean> {...}
}
Both secured.guard.ts and secured.decorator.ts are part of secured.module.ts
#Module({
imports: [
SecuredGuard,
AuthModule
],
exports: [
SecuredGuard
],
providers: [
AuthService
]
})
export class SecuredModule {}
Which is using the AuthService being exported from auth.module.ts
#Module({
controllers: [
AuthController
],
providers: [
AuthService
],
imports: [
EmailModule
],
exports: [
AuthService
]
})
export class AuthModule { }
And secured.module.ts is being imported by app.module.ts
#Module({
imports: [
SecuredModule
],
controllers: [
AppController
],
providers: [
AppService
],
})
export class AppModule { }
I don't know if I'm using the appropriate approach, or even if it's possible what I'm trying to do, any clues would be really appreciated!
In general, your solution seems to work, see this running example:
However, there are some mistakes in your module declarations:
1) In your AppModule, the AuthService is not available, since neither is the AuthModule imported directly or exported by the SecuredModule. That's why you're getting the error.
2) You don't have to declare your guards in any module, they will just be available globally. Only put modules in your imports array.
3) You're providing the AuthService multiple times, so you will have different instances of it. You should only provide it once and then only export (or re-export) your provider, but not provide it again.
4) ReflectMetadata was deprecated in v6; use SetMetadata instead.
Is it possible to initialize guard with a specifig value ?
For example the current example will not work:
#Module({
imports: [
CoreModule,
],
providers: [
{
provide: AuthGuard, // while using APP_GUARD works
useFactory: (configService: ConfigService) => {
return new AuthGuard(configService.get('some_key'));
},
inject: [ConfigService],
},
],
})
While using APP_GUARD for provide will initialise the guard with config value. So it works only for global scope, but not for #UseGuards(AuthGuard)
This doesn't work because guards are not registered as providers in a module. They get directly instantiated by the framework.
You can either use dependency injection in the guard:
#Injectable()
export class MyAuthGuard {
constructor(private readonly configService: ConfigService) {
// use the configService here
}
}
and
#UseGuards(MyAuthGuard)
or instantiate the guard yourself:
#UseGuards(new AuthGuard(configService.get('some_key')))
In the special case of the AuthGuard, you can set a defaultStrategy in the PassportModule. Then you can just use #UseGuards(AuthGuard())
PassportModule.register({ defaultStrategy: 'jwt'})
or async:
PassportModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({ defaultStrategy: configService.authStrategy}),
inject: [ConfigService],
})
Let's say you want your specific guard instance to perform differently depending on some input, basically be able to configure it. There is no option to consume this config from constructor(). Factory way might look like a bit bulky solution. But you're still able to utilise static methods to achieve wanted behaviour.
Example:
#Injectable()
class SomeController {
#Get()
#UseGuard(AuthGuard) // but how to pass smth inside AuthGuard?
public async doSomething() {}
}
Solution:
// [auth.guard.ts] file
import { UnauthorizedException, Injectable } from '#nestjs/common';
import type { CanActivate, ExecutionContext } from '#nestjs/common';
import type { GuardOptions, PatchedRequest } from './auth.types';
export interface GuardOptions {
allowAnonymous?: boolean,
allowExpired?: boolean,
}
#Injectable()
export class AuthGuard
implements CanActivate {
public options: GuardOptions = {};
public canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> {
// Guard logic
return true;
}
static configure(options: GuardOptions) {
const instance = new AuthGuard;
instance.options = options;
return instance;
}
}
// [someEntity.controller.ts] file
// imports...
#Injectable()
class SomeController {
#Get()
#UseGuard(AuthGuard.configure({ allowExpired: true })) // voila
public async doSomething() {}
}
Enjoy! Glory to Ukraine!
I would try ht less verbose approach and inject ConfigService directly into the AuthGuard in such a manner:
#Module({
imports: [
CoreModule,
],
providers: [
AuthGuard,
],
exports: [
AuthGuard,
],
})
#Injectable()
export default class AuthGuard {
constructor (protected readonly config: ConfigService) {
}
/*
...
*/
}