Проблема в том, что у декоратора нет доступа к this
экземпляру класса.Он определяется только один раз при определении класса, target
является прототипом класса.Чтобы получить экземпляр класса, он должен украсить метод или конструктор класса (расширить класс) и получить из него this
.
Это особый случай this проблема .jobCreated
используется как обратный вызов, поэтому он должен быть привязан к контексту.Кратчайший способ сделать это - определить его как стрелку:
@Subscribe(JobCreated)
jobCreated = (events: Observable<JobCreated>) => {
console.log(this) // undefined
}
Однако это, скорее всего, не сработает из-за того, что Subscribe
украшает прототип класса, а стрелки определены наэкземпляр класса.Для правильной обработки Subscribe
должен дополнительно правильно обрабатывать свойства, как показано в в этом ответе .Есть некоторые проблемы проектирования , почему функции прототипа должны быть предпочтительнее стрелок, и это одна из них.
Декоратор может взять на себя ответственность за привязку метода к контексту.Поскольку метод экземпляра не существует в момент оценки декоратора, процесс подписки следует отложить до тех пор, пока он не будет.Если в классе доступны исправления для жизненного цикла, которые можно пропатчить, класс должен быть расширен в ловушке жизненного цикла, чтобы дополнить конструктор функциями подписки:
export function EventHandler (topicKey: any): ClassDecorator {
return function (target: any) {
// run only once per class
if (Reflect.hasOwnMetadata('subscriptions', target.prototype))
return target;
target = class extends (target as { new(...args): any; }) {
constructor(...args) {
super(...args);
const topic = Container.get<DomainTopicInterface>(topicKey)
topic.subscribe(event => {
if (subscriptions.length === 0) {
throw new Error(`Event received for '${target.constructor.name}'`)
}
subscriptions.forEach((subscription: any) => {
this[subscription.methodName](event); // this is available here
})
})
}
} as any;
export function Subscribe (targetClass: StaticDomainEvent<any>): MethodDecorator {
return function (target: any, methodName: string, descriptor: TypedPropertyDescriptor<any>) {
// target is class prototype
let subscriptions = Reflect.getOwnMetadata('subscriptions', target);
subscriptions.push({
methodName,
targetClass
// no `callback` because parent method implementation
// doesn't matter in child classes
})
}
}
Обратите внимание, что подписка происходит после super
,это позволяет при необходимости привязывать методы в исходном конструкторе класса к другим контекстам.
Reflect
API метаданных также можно заменить обычными свойствами, в частности символами.