简体   繁体   English

在 Nest.js 中,如何在装饰器中获取服务实例?

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

In CustomDecorator , how to access a service instance defined in Nest.js?CustomDecorator ,如何访问 Nest.js 中定义的服务实例?

export const CustomDecorator = (): MethodDecorator => {
  return (
    target: Object,
    propertyKey: string | symbol,
    descriptor: PropertyDescriptor
    ) => {

    // Here, is possibile to access a Nest.js service (i.e. TestService) instance?

    return descriptor;
  }
};

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.在创建decorated instance之前执行的属性装饰器。
  • Decorator want to use some instance resolved by Injector of decorated instance .装饰器想要使用decorated instance的注入器解析的some instance decorated instance

As a straightforward way - use some instance injected by decorated instance .作为一种直接的方法 - 使用decorated instance注入的some instance 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 ). PS 我认为以某种方式可以使用 Injector 的实例来获得任何所需的实例(如angular 那样)。

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.这可能不适合所有用例,但我能够复制 Nest 核心包中的通用模式以满足我的需求。

I wanted to create my own decorator for annotating controller methods to handle events (eg, @Subscribe('some.topic.key') async handler() { ... }) ).我想创建我自己的装饰器来注释控制器方法来处理事件(例如, @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).为了实现这一点,我的装饰用SetMetadata@nestjs/common注册我所需的一些元数据(它被应用到方法名,它属于类,该方法的引用)。

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, eg:从那里,我能够创建自己的模块,该模块连接到 Nest 的生命周期钩子,以查找我用我的装饰器装饰的所有方法,并对其应用一些逻辑,例如:

@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.资源管理器服务使用@nestjs/core一些实用程序来内省容器并处理查找所有带有元数据的装饰函数。

@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我修改了此处提供的代码: https : //github.com/nestjs/nest/blob/5.1.0-stable/packages/microservices/listener-metadata-explorer.ts

I was trying to use my config service inside a ParamDecorator, so I access my service by creating a new instance of it :我试图在 ParamDecorator 中使用我的配置服务,所以我通过创建它的新实例来访问我的服务:

export const MyParamDecorator = createParamDecorator((data, req) => {

  // ...
  const configService = new ConfigService(`${process.env.NODE_ENV || 'default'}.env`);
  const myConfigValue = configService.getMyValue();
  // ...
});

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM