Как получить доступ к метаданным класса из декоратора методов

У меня два декоратора. Декоратор класса и декоратор метода. Декоратор класса определяет метаданные, к которым я хочу получить доступ в декораторе метода.

ClassDecorator:

function ClassDecorator(topic?: string): ClassDecorator {
    return (target) => {
        Reflect.defineMetadata('topic', topic, target);
        // I've also tried target.prototype instead of target
        return target;
    };
}

MethodDecorator:

interface methodDecoratorOptions {
    cmd: string
}

function MethodDecorator(options: decoratorOptions) {
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
        // HERE IS MY PROBLEM
        console.log('metaData is: ', Reflect.getMetadata('topic', target));
    }
}

И это определение моего класса:

@ClassDecorator('auth')
export class LoginClass {

    @MethodDecorator({
        cmd: 'login'
    })
    myMethod() {
        console.log('METHOD CALLED');
    }
}

ПРОБЛЕМА:

Следующая строка MethodDecorator возвращает metaData is: undefined. Почему не определено?

console.log('metaData is: ', Reflect.getMetadata('topic', target));

ВОПРОС:

Как я могу получить доступ к метаданным, определенным ClassDecorator, из MethodDecorator?


person tmuecksch    schedule 19.01.2018    source источник


Ответы (1)


Проблема в том, в каком порядке выполняются декораторы. Сначала выполняются декораторы методов, затем - декораторы классов. Если задуматься, это имеет смысл: декораторам классов нужен полный класс для работы, а создание класса включает в себя создание методов и вызов их декораторов в первую очередь.

Простым обходным решением было бы для декоратора метода зарегистрировать обратный вызов, который затем будет вызываться декоратором класса после установки темы:

function ClassDecorator(topic?: string): ClassDecorator {
    return (target) => {
        Reflect.defineMetadata('topic', topic, target.prototype);
        let topicFns: Array<() => void> = Reflect.getMetadata("topicCallbacks", target.prototype);
        if (topicFns) {
            topicFns.forEach(fn => fn());
        }
        return target;
    };
}

interface methodDecoratorOptions {
    cmd: string
}

function MethodDecorator(options: methodDecoratorOptions) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        let topicFns: Array<() => void> = Reflect.getMetadata("topicCallbacks", target);
        if (!topicFns) {
            Reflect.defineMetadata("topicCallbacks", topicFns = [], target);
        }
        topicFns.push(() => {
            console.log('metaData is: ', Reflect.getMetadata('topic', target));
        });
    }
}

@ClassDecorator('auth')
class LoginClass {

    @MethodDecorator({
        cmd: 'login'
    })
    myMethod() {
        console.log('METHOD CALLED');
    }
}
person Titian Cernicova-Dragomir    schedule 19.01.2018
comment
Но, честно говоря, мне не нравится этот обходной путь. Мне это кажется менее читаемым. Я надеюсь найти лучшее решение. - person tmuecksch; 19.01.2018
comment
Я не знаю другой работы, это затрудняет чтение кода ... но опять же декораторы являются частью вашей инфраструктуры, которую вы напишете один раз и будете использовать много раз, а использование декораторов не изменится, что является на мой взгляд более важная часть - person Titian Cernicova-Dragomir; 19.01.2018
comment
Вы можете снять отметку с ответа и дождаться лучшего обходного пути, я не против :). Но я видел и использовал аналогичные обходные пути для этой проблемы и пока не нашел лучшего, только вариации на эту тему (например, используйте поле статического класса вместо Reflect` api) - person Titian Cernicova-Dragomir; 19.01.2018
comment
Я не хотел тебя обидеть. Я очень благодарен за то, что теперь понимаю проблему. Я не понимаю, почему разработчики выбрали именно такой порядок выполнения декоратора. Может быть, у них на то есть причина, но для меня это неудобно. - person tmuecksch; 19.01.2018
comment
@tmuecksch Без обид, правда :). Я уверен, что где-то есть стандарт, который требует этого. И я уверен, что они рассматривали другой способ, но такой порядок имеет больше смысла для большинства случаев использования, к сожалению, не для вашего. - person Titian Cernicova-Dragomir; 19.01.2018