web-dev-qa-db-fra.com

Dans Nest.js, comment obtenir une instance de service dans un décorateur?

Dans CustomDecorator, comment accéder à une instance de service définie dans 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;
  }
};
15
gremo

Nous avons quelques points:

  • Décorateur de propriété exécuté avant decorated instance sera créé.
  • Le décorateur souhaite utiliser some instance résolu par l'injecteur de decorated instance.

Comme méthode simple - utilisez some instance injecté par 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 je pense qu'il est en quelque sorte possible d'utiliser une instance d'Injector pour obtenir l'une des instances souhaitées (comme angular does ).

6
Buggy

Je suis tombé sur cette question et j'ai passé la journée à essayer de trouver une bonne réponse. Cela peut ne pas convenir à tous les cas d'utilisation, mais j'ai pu copier un modèle commun dans le package principal de Nest pour répondre à mes besoins.

Je voulais créer mon propre décorateur pour annoter les méthodes de contrôleur pour gérer les événements (par exemple, @Subscribe('some.topic.key') async handler() { ... })).

Pour implémenter cela, mon décorateur a utilisé SetMetadata de @nestjs/common pour enregistrer certaines métadonnées dont j'avais besoin (le nom de la méthode à laquelle il était appliqué, la classe à laquelle il appartenait, une référence à la méthode).

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

À partir de là, j'ai pu créer mon propre module qui s'est accroché aux crochets du cycle de vie de Nest pour trouver toutes les méthodes que j'avais décorées avec mon décorateur, et y appliquer une logique, par exemple:

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

Le service Explorer a utilisé certains utilitaires dans @nestjs/core pour introspecter le conteneur et gérer la recherche de toutes les fonctions décorées avec leurs métadonnées.

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

Je ne pense pas que ce soit la meilleure façon de gérer cela, mais cela a bien fonctionné pour moi. Utilisez ce code à vos risques et périls, il devrait vous aider à démarrer :-). J'ai adapté le code disponible à partir d'ici: https://github.com/nestjs/nest/blob/5.1.0-stable/packages/microservices/listener-metadata-Explorer.ts

3
Mavrick Laakso

Tard dans la soirée, mais comme j'avais un problème similaire ( tiliser le module global nest dans le décorateur ) et que je suis tombé sur cette 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);
  };
}

J'essayais d'utiliser mon service de configuration dans un ParamDecorator, donc j'accède à mon service en en créant une nouvelle instance:

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

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