In Nest.js, how to get a service instance inside a decorator? In Nest.js, how to get a service instance inside a decorator? typescript typescript

In Nest.js, how to get a service instance inside a decorator?


Late to the party, but since I had a similar problem (Use global nest module in decorator) and stumbled upon this question.

import { Inject } from '@nestjs/common';export function yourDecorator() {  const injectYourService = Inject(YourServiceClass);  return (target: any, propertyKey: string, propertyDescriptor: PropertyDescriptor) => {    // this is equivalent to have a constructor like constructor(yourservice: YourServiceClass)    // note that this will injected to the instance, while your decorator runs for the class constructor    injectYourService(target, 'yourservice');    // do something in you decorator    // we use a ref here so we can type it    const yourservice: YourServiceClass = this.yourservice;    yourservice.someMethod(someParam);  };}


We have a few point:

  • Property decorator executed before decorated instance will be created.
  • Decorator want to use some instance resolved by Injector of decorated instance.

As a straightforward way - use some instance injected by decorated instance.

@Injectable()export class CatsService {  constructor(public myService: MyService){}  @CustomDecorator()  foo(){}}export const CustomDecorator = (): MethodDecorator => {  return (    target: Object,    propertyKey: string | symbol,    descriptor: PropertyDescriptor  ) => {    const originalMethod = descriptor.value;    descriptor.value = function () {      const serviceInstance = this;      console.log(serviceInstance.myService);    }    return descriptor;  }};

PS i think it is somehow possible to use instance of Injector to get any of desired instances (like angular does).


Came across this question and spent the day trying to figure out a good answer. This may not fit every use case, but I was able to copy a common pattern in Nest's core package to suit my needs.

I wanted to create my own decorator for annotating controller methods to handle events (e.g, @Subscribe('some.topic.key') async handler() { ... })).

To implement this, my decorator used SetMetadata from @nestjs/common to register some metadata I required (the method name it was being applied to, the class it belonged to, a reference to the method).

export const Subscribe = (topic: string) => {  return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {    SetMetadata<string, RabbitSubscriberMetadataConfiguration>(      RABBITMQ_SUBSCRIBER,      {        topic,        target: target.constructor.name,        methodName: propertyKey,        callback: descriptor.value,      },    )(target, propertyKey, descriptor);  };};

From there, I was able to create my own module which hooked into Nest's lifecycle hooks to find all methods I had decorated with my decorator, and apply some logic to it, e.g:

@Module({  imports: [RabbitmqChannelProvider],  providers: [RabbitmqService, MetadataScanner, RabbitmqSubscriberExplorer],  exports: [RabbitmqService],})export class RabbitmqModule implements OnModuleInit {  constructor(    private readonly explorer: RabbitmqSubscriberExplorer,    private readonly rabbitmqService: RabbitmqService,  ) {}  async onModuleInit() {    // find everything marked with @Subscribe    const subscribers = this.explorer.explore();    // set up subscriptions    for (const subscriber of subscribers) {      await this.rabbitmqService.subscribe(        subscriber.topic,        subscriber.callback,      );    }  }}

The explorer service used some utilities in @nestjs/core to introspect the container and handle finding all the decorated functions with their metadata.

@Injectable()export class RabbitmqSubscriberExplorer {  constructor(    private readonly modulesContainer: ModulesContainer,    private readonly metadataScanner: MetadataScanner,  ) {}  public explore(): RabbitSubscriberMetadataConfiguration[] {    // find all the controllers    const modules = [...this.modulesContainer.values()];    const controllersMap = modules      .filter(({ controllers }) => controllers.size > 0)      .map(({ controllers }) => controllers);    // munge the instance wrappers into a nice format    const instanceWrappers: InstanceWrapper<Controller>[] = [];    controllersMap.forEach(map => {      const mapKeys = [...map.keys()];      instanceWrappers.push(        ...mapKeys.map(key => {          return map.get(key);        }),      );    });    // find the handlers marked with @Subscribe    return instanceWrappers      .map(({ instance }) => {        const instancePrototype = Object.getPrototypeOf(instance);        return this.metadataScanner.scanFromPrototype(          instance,          instancePrototype,          method =>            this.exploreMethodMetadata(instance, instancePrototype, method),        );      })      .reduce((prev, curr) => {        return prev.concat(curr);      });  }  public exploreMethodMetadata(    instance: object,    instancePrototype: Controller,    methodKey: string,  ): RabbitSubscriberMetadataConfiguration | null {    const targetCallback = instancePrototype[methodKey];    const handler = Reflect.getMetadata(RABBITMQ_SUBSCRIBER, targetCallback);    if (handler == null) {      return null;    }    return handler;  }}

I am not espousing this as being the best way to handle this, but it has worked well for me. Use this code at your own risk, it should get you started :-). I adapted the code available from here: https://github.com/nestjs/nest/blob/5.1.0-stable/packages/microservices/listener-metadata-explorer.ts