Объедините несколько привязок хоста в один декоратор - PullRequest
0 голосов
/ 12 декабря 2018

У меня есть компоненты, которые используют декораторы привязки хоста для отображения селекторов компонентов, чтобы они брали всю доступную ширину:

@HostBinding('style.width.%')
@HostBinding('style.height.%')
private readonly SIZE = 100;

@HostBinding('style.display')
private readonly DISPLAY = 'block';

@HostBinding('style.box-sizing')
private readonly BOX_MODEL = 'border-box';

Что я хотел бы сделать, это создать один декоратор (на уровне класса или свойства(Мне все равно), который включает в себя все эти, так что мне не нужно переписывать это каждый раз.

Прямо сейчас, я заставил это работать, создав суперкласс и позволив другим классам расширить его, но у него много ограничений и неудобств, отсюда и мой вопрос.

Любая помощь, даже если это документация, очень ценится!

РЕДАКТИРОВАТЬ Я также нашел этот вопрос SOF , но разница с моимпроблема в том, что переменные имеют значение, и я не могу найти способ передать эти значения своему декоратору.

РЕДАКТИРОВАТЬ 2 Чтобы объяснить мою потребность: у меня есть этот компонент

@Component(...)
export class MyAngularComponent extends HostBinderComponent {...}

и этот

export class HostBinderComponent {
  @HostBinding('style.width.%')
  @HostBinding('style.height.%')
  private readonly SIZE = 100;

  @boxComponent()
  private readonly DISPLAY;

  @HostBinding('style.box-sizing')
  private readonly BOX_MODEL = 'border-box';
}

И моей конечной целью было бы удалить суперкласс и получить что-то вроде

@Component(...)
@BoxComponent()
export class MyAngularComponent {...}

, чтобы у меня не былобольше использовать extends или HostBinding в моих угловых компонентах!

Ответы [ 2 ]

0 голосов
/ 13 декабря 2018

После многих испытаний мне удалось найти очень простой и понятный способ сделать то, чего я хотел достичь.

После получения ответа @ TitanCernicovaDragomir и исходного кода декораторов мне удалось понять, что:

  • Декодер привязки хостатребуется значение (в отличие от этот ответ )
  • Декоратор использует имя переменной (= key, предоставленное декоратором)
  • Он находит ключв target (также предоставляется декоратором)
  • Декоратор возвращает функцию, которая просто нуждается в этой цели и ключе

, которая, в конце концов, дает этот очень простой код:

export const boxComponent: ClassDecorator = (component) => {
  const bindings = [
    { id: 'HOST_BINDINGS_WIDTH', ngStyle: 'style.width.%', value: 100},
    { id: 'HOST_BINDINGS_HEIGHT', ngStyle: 'style.height.%', value: 100},
    { id: 'HOST_BINDINGS_DISPLAY', ngStyle: 'style.display', value: 'block'},
    { id: 'HOST_BINDINGS_BOX_MODEL', ngStyle: 'style.box-sizing', value: 'border-box'},
  ];

  bindings.forEach(binding => {
    component.prototype[binding.id] = binding.value;

    const hostBindingFn = HostBinding(binding.ngStyle);
    hostBindingFn(component.prototype, binding.id);
  });
};

В качестве идентификатора я привязал свои переменные к прототипу моего компонента (то есть target BTW), чтобы переменные не мешали моему существующему компоненту, но он должен работать, не касаясьпрототип!

0 голосов
/ 12 декабря 2018

Применение декоратора включает вызов вспомогательной функции __decorate.Эта функция может быть сгенерирована компилятором или использована из пользовательского tslib.Angular использует модуль tslib для функции __decorate, и мы можем использовать __decorate оттуда.(Мы могли бы скопировать версию, сгенерированную ts, или написать нашу собственную более простую версию __decorate, которая вызывает функцию декоратора, но лучше всего использовать тот же способ вызова декораторов, что и фреймворк)

Armedс помощью этой функции (и после проверки того, как TS вызывает декоратор для полей), мы можем легко создать наш собственный составной декоратор:

import { __decorate } from 'tslib';


function BoxHostBindings() {
  return function(target) {
    __decorate([
      HostBinding('style.width.%'),
      HostBinding('style.height.%'),
    ], target.prototype, "SIZE", void 0);
    __decorate([
      HostBinding('style.display'),
    ], target.prototype, "DISPLAY", void 0);
    __decorate([
      HostBinding('style.box-sizing'),
    ], target.prototype, "BOX_MODEL", void 0);
  }
}

Примечание : я не проверял это подробно, ноЭто, кажется, работает, и я ожидал бы, что это будет работать.

Использование:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
@BoxHostBindings()
export class AppComponent {
  title = 'testapp';

  private readonly SIZE = 100;
  private readonly DISPLAY = 'block'
  private readonly BOX_MODEL = 'border-box';
}

Редактировать

Установка значений для свойствнемного сложнее.У нас нет легкого доступа к выполнению конструктора, поэтому мы не можем сделать это тогда.

Один из вариантов - определить значения для prototype, так как значения доступны только для чтения, это должно работать нормально:

function BoxHostBindings(size: number, display: string, box_model: string) {
  return function(target) {
    target.prototype.SIZE = size;
    target.prototype.DISPLAY = display;
    target.prototype.BOX_MODEL = box_model;
    __decorate([
      HostBinding('style.width.%'),
      HostBinding('style.height.%'),
    ], target.prototype, "SIZE", void 0);
    __decorate([
      HostBinding('style.display'),
    ], target.prototype, "DISPLAY", void 0);
    __decorate([
      HostBinding('style.box-sizing'),
    ], target.prototype, "BOX_MODEL", void 0);
  }
}

Мы также можем определить свойства, чтобы позволить пользователюизменить значение и сохранить его в поле ('_' + имя), но вернуть значение по умолчанию, если значение не определено:

function BoxHostBindings(size: number, display: string, box_model: string) {
  return function(target) {
    function propHelper(name: string, defaultValue: any) {
      Object.defineProperty(target.prototype, name, {
        get: function () {
            return this['_' + name] || defaultValue;
        },
        set: function(value: any ) {
          this['_' + name] = value
        },
        enumerable: true,
        configurable: true
      });
    }
    propHelper("SIZE", size);
    propHelper("DISPLAY", display);
    propHelper("BOX_MODEL", box_model);
    __decorate([
      HostBinding('style.width.%'),
      HostBinding('style.height.%'),
    ], target.prototype, "SIZE", void 0);
    __decorate([
      HostBinding('style.display'),
    ], target.prototype, "DISPLAY", void 0);
    __decorate([
      HostBinding('style.box-sizing'),
    ], target.prototype, "BOX_MODEL", void 0);
  }
}
...