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;
}
};
Nous avons quelques points:
decorated instance
sera créé.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 ).
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
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();
// ...
});