将多个主机绑定组合到一个装饰器中

时间:2018-12-12 14:15:24

标签: angular typescript decorator

我有一些使用主机绑定装饰器显示组件选择器的组件,以便它们采用所有可用宽度:

@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';

我想做的是创建一个包含所有这些装饰器(在类或属性级别,我不在乎),这样我就不必每次都重写它。

现在,我通过创建一个超类并让我的其他类对其进行扩展来使其起作用,但是它有很多局限性和不便之处,因此我提出了问题。

任何帮助,即使它是文档,也非常感谢!

编辑我也发现了this SOF question,但是与我的问题不同的是变量具有值,而且我似乎找不到如何将这些值传递给我的方法。装饰器。

编辑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 {...}

这样我就不必在Angular组件中使用extendsHostBinding了!

2 个答案:

答案 0 :(得分:4)

应用装饰器涉及调用__decorate帮助器函数。此函数可以由编译器生成,也可以从自定义tslib使用。 Angular将tslib模块用于__decorate函数,我们可以从那里使用__decorate。 (我们可以复制ts生成的版本,也可以编写我们自己的更简单的__decorate版本来调用装饰器函数,但是最好只使用与框架所用的相同方式来调用装饰器)

使用此功能(并检查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);
  }
}

答案 1 :(得分:0)

经过大量的尝试,我设法找到了一种非常精简且易于理解的方式来完成我想要达到的目标。

经过@TitanCernicovaDragomir's answersource code of the decorators之后,我终于明白了:

  • 主机绑定装饰器需要一个值(与this answer相对)
  • 装饰器依赖于变量名(= {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)上,以使变量不会干扰我现有的组件,但它应该工作而不接触原型! >