【问题标题】:Combine several host bindings into one single decorator将多个主机绑定组合成一个装饰器
【发布时间】:2019-05-13 16:10:11
【问题描述】:

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

@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,但与我的问题不同的是变量具有值,我似乎无法找到如何将这些值传递给我的装饰器。

EDIT 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 了!

【问题讨论】:

  • 您希望装饰器仅应用于这些确切的属性?有这些确切的值吗?
  • @TitianCernicova-Dragomir 为了便于理解,是的,我只是想要一个装饰器,例如(例如)@BoxComponent,它将所有这 4 个装饰器应用于一个类(具有给定的值)
  • @trichetriche(删除最后一个代码块中的扩展)

标签: angular typescript decorator


【解决方案1】:

应用装饰器涉及调用__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);
  }
}

我们还可以定义属性以便用户修改值,并将其存储在字段中('_' + name),但如果值未定义,则返回默认值:

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);
  }
}

【讨论】:

  • 在您的解决方案中,您没有为函数赋予任何价值 (100, 'block', 'border-box'),这正常吗?
  • 该值仍将在您的类中定义..这只是结合了装饰器..而不是字段声明..您仍然需要定义字段(否则它们将为空)。您可以将它们的默认值分配给原型......这也可以工作
  • 所以我还需要写 4 次 BoxHostBindings ?
  • @trichetriche 不,它应该应用于类..但您需要声明字段。我将添加一个使用示例。
  • 对不起,我刚刚登录 SOF(昨天回家时断开了连接),但我自己尝试了一些东西,并找到了一种干净的方法。再次感谢您的回答,这(有点)向我解释了装饰师在做什么,让我找到解决问题的方法。我已经在我自己的帖子中发布了一个答案来展示我所做的事情,但我会将您的答案标记为已接受,因为如果没有 :) 我就不会这样做
【解决方案2】:

经过大量试验,我设法找到了一种非常精简且易于理解的方法来实现我想要实现的目标。

在阅读了@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),这样变量就不会干扰我现有的组件,但它应该可以在不触及原型的情况下工作!

【讨论】:

    猜你喜欢
    • 2011-01-12
    • 2015-08-05
    • 2011-07-21
    • 2015-02-22
    • 2016-12-25
    • 2011-09-03
    • 1970-01-01
    • 2013-06-09
    相关资源
    最近更新 更多