Module reference

Nest provides the ModuleRef class to navigate the internal list of providers and obtain a reference to any provider using its injection token as a lookup key. The ModuleRef class also provides a way to dynamically instantiate both static and scoped providers. ModuleRef can be injected into a class in the normal way:

cats.service.ts

  1. @Injectable()
  2. export class CatsService {
  3. constructor(private moduleRef: ModuleRef) {}
  4. }
  1. @Injectable()
  2. @Dependencies(ModuleRef)
  3. export class CatsService {
  4. constructor(moduleRef) {
  5. this.moduleRef = moduleRef;
  6. }
  7. }

Hint The ModuleRef class is imported from the @nestjs/core package.

Retrieving instances

The ModuleRef instance (hereafter we’ll refer to it as the module reference) has a get() method. This method retrieve a provider, controller, or injectable (e.g., guard, interceptor, etc.) that exists (has been instantiated) in the current module using its injection token/class name.

cats.service.ts

  1. @Injectable()
  2. export class CatsService implements OnModuleInit {
  3. private service: Service;
  4. constructor(private moduleRef: ModuleRef) {}
  5. onModuleInit() {
  6. this.service = this.moduleRef.get(Service);
  7. }
  8. }
  1. @Injectable()
  2. @Dependencies(ModuleRef)
  3. export class CatsService {
  4. constructor(moduleRef) {
  5. this.moduleRef = moduleRef;
  6. }
  7. onModuleInit() {
  8. this.service = this.moduleRef.get(Service);
  9. }
  10. }

Warning You can’t retrieve scoped providers (transient or request-scoped) with the get() method. Instead, use the technique described below. Learn how to control scopes here.

To retrieve a provider from the global context (for example, if the provider has been injected in a different module), pass the { strict: false } option as a second argument to get().

  1. this.moduleRef.get(Service, { strict: false });

Resolving scoped providers

To dynamically resolve a scoped provider (transient or request-scoped), use the resolve() method, passing the provider’s injection token as an argument.

cats.service.ts

  1. @Injectable()
  2. export class CatsService implements OnModuleInit {
  3. private transientService: TransientService;
  4. constructor(private moduleRef: ModuleRef) {}
  5. async onModuleInit() {
  6. this.transientService = await this.moduleRef.resolve(TransientService);
  7. }
  8. }
  1. @Injectable()
  2. @Dependencies(ModuleRef)
  3. export class CatsService {
  4. constructor(moduleRef) {
  5. this.moduleRef = moduleRef;
  6. }
  7. async onModuleInit() {
  8. this.transientService = await this.moduleRef.resolve(TransientService);
  9. }
  10. }

The resolve() method returns a unique instance of the provider, from its own DI container sub-tree. Each sub-tree has a unique context identifier. Thus, if you call this method more than once and compare instance references, you will see that they are not equal.

cats.service.ts

  1. @Injectable()
  2. export class CatsService implements OnModuleInit {
  3. constructor(private moduleRef: ModuleRef) {}
  4. async onModuleInit() {
  5. const transientServices = await Promise.all([
  6. this.moduleRef.resolve(TransientService),
  7. this.moduleRef.resolve(TransientService),
  8. ]);
  9. console.log(transientServices[0] === transientServices[1]); // false
  10. }
  11. }
  1. @Injectable()
  2. @Dependencies(ModuleRef)
  3. export class CatsService {
  4. constructor(moduleRef) {
  5. this.moduleRef = moduleRef;
  6. }
  7. async onModuleInit() {
  8. const transientServices = await Promise.all([
  9. this.moduleRef.resolve(TransientService),
  10. this.moduleRef.resolve(TransientService),
  11. ]);
  12. console.log(transientServices[0] === transientServices[1]); // false
  13. }
  14. }

To generate a single instance across multiple resolve() calls, and ensure they share the same generated DI container sub-tree, you can pass a context identifier to the resolve() method. Use the ContextIdFactory class to generate a context identifier. This class provides a create() method that returns an appropriate unique identifier.

cats.service.ts

  1. @Injectable()
  2. export class CatsService implements OnModuleInit {
  3. constructor(private moduleRef: ModuleRef) {}
  4. async onModuleInit() {
  5. const contextId = ContextIdFactory.create();
  6. const transientServices = await Promise.all([
  7. this.moduleRef.resolve(TransientService, contextId),
  8. this.moduleRef.resolve(TransientService, contextId),
  9. ]);
  10. console.log(transientServices[0] === transientServices[1]); // true
  11. }
  12. }
  1. @Injectable()
  2. @Dependencies(ModuleRef)
  3. export class CatsService {
  4. constructor(moduleRef) {
  5. this.moduleRef = moduleRef;
  6. }
  7. async onModuleInit() {
  8. const contextId = ContextIdFactory.create();
  9. const transientServices = await Promise.all([
  10. this.moduleRef.resolve(TransientService, contextId),
  11. this.moduleRef.resolve(TransientService, contextId),
  12. ]);
  13. console.log(transientServices[0] === transientServices[1]); // true
  14. }
  15. }

Hint The ContextIdFactory class is imported from the @nestjs/core package.

Getting current sub-tree

Occasionally, you may want to resolve an instance of a request-scoped provider within a request context. Let’s say that CatsService is request-scoped and you want to resolve the CatsRepository instance which is also marked as a request-scoped provider. In order to share the same DI container sub-tree, you must obtain the current context identifier instead of generating a new one (e.g., with the ContextIdFactory.create() function, as shown above). To obtain the current context identifier, start by injecting the request object using @Inject() decorator.

cats.service.ts

  1. @Injectable()
  2. export class CatsService {
  3. constructor(
  4. @Inject(REQUEST) private request: Record<string, unknown>,
  5. ) {}
  6. }
  1. @Injectable()
  2. @Dependencies(REQUEST)
  3. export class CatsService {
  4. constructor(request) {
  5. this.request = request;
  6. }
  7. }

Hint Learn more about the request provider here.

Now, use the getByRequest() method of the ContextIdFactory class to create a context id based on the request object, and pass this to the resolve() call:

  1. const contextId = ContextIdFactory.getByRequest(this.request);
  2. const catsRepository = await this.moduleRef.resolve(CatsRepository, contextId);

Instantiating custom classes dynamically

To dynamically instantiate a class that wasn’t previously registered as a provider, use the module reference’s create() method.

cats.service.ts

  1. @Injectable()
  2. export class CatsService implements OnModuleInit {
  3. private catsFactory: CatsFactory;
  4. constructor(private moduleRef: ModuleRef) {}
  5. async onModuleInit() {
  6. this.catsFactory = await this.moduleRef.create(CatsFactory);
  7. }
  8. }
  1. @Injectable()
  2. @Dependencies(ModuleRef)
  3. export class CatsService {
  4. constructor(moduleRef) {
  5. this.moduleRef = moduleRef;
  6. }
  7. async onModuleInit() {
  8. this.catsFactory = await this.moduleRef.create(CatsFactory);
  9. }
  10. }

This technique enables you to conditionally instantiate different classes outside of the framework container.

Hoodies, T-shirts, and accessories!

Support our future development by shopping in the official store!

See more