Cannot inject custom provider for dynamic module

252 views Asked by At

I tried to create a dynamic module PermissionModule like the following:

permTestApp.module.ts

@Module({
  imports: [PermissionModule.forRoot({ text: 'abc' })],
  providers: [],
  controllers: [PermissionTestController],
})
export class PermissionTestAppModule {}

permission.module.ts

import { DynamicModule, Module } from '@nestjs/common'
import { PermissionGuard } from './guard/permission.guard'

@Module({})
export class PermissionModule {
  public static forRoot(config: { text: string }): DynamicModule {
    return {
      module: PermissionModule,
      providers: [
        {
          provide: 'xoxo',
          useValue: config.text,
        },
        PermissionGuard,
      ],
      exports: [PermissionGuard],
    }
  }
}

permission.guard.ts

import {
  CanActivate,
  ExecutionContext,
  Inject,
  Injectable,
} from '@nestjs/common'

@Injectable()
export class PermissionGuard implements CanActivate {
  constructor(@Inject('xoxo') private readonly config: string) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    console.log(31, this.config)
    return true
  }
}

AFAIK, 'abc' string must be injected when PermissionGuard is used.

I tried to test it with the following code.

permission.e2e.spec.ts

  beforeAll(async () => {
    const moduleRef: TestingModule = await Test.createTestingModule({
      imports: [PermissionTestAppModule],
    })
      .compile()

    app = moduleRef.createNestApplication()
    controller = await moduleRef.resolve(PermissionTestController)
    await app.init()
  })

but it says,

    Nest can't resolve dependencies of the PermissionGuard (?). Please make sure that the argument xoxo at index [0] is available in the PermissionTestAppModule context.

    Potential solutions:
    - Is PermissionTestAppModule a valid NestJS module?
    - If xoxo is a provider, is it part of the current PermissionTestAppModule?
    - If xoxo is exported from a separate @Module, is that module imported within PermissionTestAppModule?
      @Module({
        imports: [ /* the Module containing xoxo */ ]
      })

What am I doing wrong?

1

There are 1 answers

0
mwieczorek On

You will need to export the injection token from the module. The reason is that the global context needs to know the existence of that injection token when trying to inject the dependency into a guard, middleware, interceptor, or filter - anything that happens outside the context of your PermissionModule.

Also, make it global so you won't need to import it into each module. Optional, but depends on your use case.

import { DynamicModule, Module } from '@nestjs/common'
import { PermissionGuard } from './guard/permission.guard'

@Module({})
export class PermissionModule {
  public static forRoot(config: { text: string }): DynamicModule {
    return {
      global: true, // <-- add this optionally
      module: PermissionModule,
      providers: [
        {
          provide: 'xoxo',
          useValue: config.text,
        },
        PermissionGuard,
      ],
      exports: [
        PermissionGuard,
        'xoxo',  // <-- add this
      ],
    }
  }
}

I suggest using a symbol for injection tokens, and keeping it in a constants file somewhere and importing it wherever you need it, so if that token ever needs to change, you know... DRY.

export const PERMISSION_CONFIG = Symbol('PERMISSION_CONFIG')

Hope this helps.