В 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;
  }
};

Ответ 1

У нас есть несколько моментов:

  • Декоратор свойств, выполненный до создания decorated instance.
  • Декоратор хочет использовать some 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 Я думаю, что каким-то образом можно использовать экземпляр Injector, чтобы получить любой из желаемых экземпляров (как это делает angular).

Ответ 2

Наткнулся на этот вопрос и провел день, пытаясь найти хороший ответ. Это может не подходить для каждого варианта использования, но я смог скопировать общий шаблон в базовом пакете Nest для своих нужд.

Я хотел создать свой собственный декоратор для аннотирования методов контроллера для обработки событий (например, @Subscribe('some.topic.key') async handler() {... })).

Чтобы реализовать это, мой декоратор использовал 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);
  };
};

Оттуда я смог создать свой собственный модуль, который подключался к перехватчикам жизненного цикла 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,
      );
    }
  }
}

Служба проводника использовала некоторые утилиты в @nestjs/core чтобы @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;
  }
}

Я не поддерживаю это как лучший способ справиться с этим, но это хорошо сработало для меня. Используйте этот код на свой страх и риск, он должен начать :-). Я адаптировал код, доступный здесь: https://github.com/nestjs/nest/blob/5.1.0-stable/packages/microservices/listener-metadata-explorer.ts

Ответ 3

Я пытался использовать свой сервис конфигурации внутри ParamDecorator, поэтому я обращаюсь к своему сервису, создавая его новый экземпляр:

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

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