В 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();
// ...
});