javascript - În Nest.js, cum să obții o instanță a unui serviciu într-un decorator?


În CustomDecorator, cum se poate accesa o instanță de serviciu definită în 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;
  }
};

Răspuns #1

Târziu la petrecere, dar pentru că am avut o problemă similară (Folosirea modulului global Nest în decorator) și am dat peste această întrebare.

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

Răspunsul #2

Avem câteva puncte:

  • Decoratorul de proprietate a fost executat înainte ca instanța decorată să fie creată.
  • Decoratorul vrea să folosească o instanță rezolvată de Injector al instanței decorate.

Ca o modalitate directă - folosește unei instanțe injectată de instanța decorată.

@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 cred că este cumva posibil să folosești o instanță de Injector pentru a obține oricare dintre instanțele dorite (așa cum face Angular).

Răspunsul #3

Am dat peste această întrebare și am petrecut ziua încercând să găsesc un răspuns bun. Acesta poate să nu se potrivească fiecărui caz de utilizare, dar am reușit să copiez un tipar comun din pachetul de bază al lui Nest pentru a-mi satisface nevoile.

Am vrut să creez propriul meu decorator pentru a adnota metodele controller-ului pentru a gestiona evenimente (de exemplu, @Subscribe('some.topic.key') async handler() { ... })).

Pentru a implementa acest lucru, decoratorul meu a folosit SetMetadata din @nestjs/common pentru a înregistra niște metadate de care aveam nevoie (numele metodei la care era aplicat, clasa căreia îi aparținea, o referință la metodă).

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

De acolo, am reușit să creez propriul meu modul care se conecta la hook-urile de ciclu de viață ale Nest pentru a găsi toate metodele pe care le decorasem cu decoratorul meu și a aplica niște logică acestora, de exemplu:

@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,
      );
    }
  }
}

Serviciul explorer a folosit câteva utilitare din @nestjs/core pentru a introspecta containerul și a gestiona identificarea tuturor funcțiilor decorate cu metadatele lor.

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

Nu susțin că aceasta este cea mai bună modalitate de a gestiona aceasta, dar a funcționat bine pentru mine. Folosește acest cod pe propriul risc, ar trebui să te ajute să începi :-). Am adaptat codul disponibil de aici: https://github.com/nestjs/nest/blob/5.1.0-stable/packages/microservices/listener-metadata-explorer.ts