【问题标题】:How to pass changeDetectorRef to decorator?如何将 changeDetectorRef 传递给装饰器?
【发布时间】:2018-02-05 22:52:26
【问题描述】:

我正在为属性创建自己的装饰器,它将自动将属性的值与 firebase db 同步。我的装饰器非常简单,看起来像这样:

export function AutoSync(firebasePath: string) {
    return (target: any, propertyKey: string) => {
        let   _propertyValue = target[propertyKey];

        // get reference to db
        const _ref           = firebase.database().ref(firebasePath);

        // listen for data from db
        _ref.on('value', snapshot => {
            _propertyValue = snapshot.val();
        });

        Object.defineProperty(target, propertyKey, {
            get: () => _propertyValue,
            set: (v) => {
                _propertyValue = v;
                _ref.set(v);
            },
        });
    }
}

我是这样使用的:

@AutoSync('/config') configuration;

而且它(几乎)就像一个魅力。我的configuration 属性自动与路径/config 上的firebase db 对象同步。属性设置器会自动更新 db 中的值 - 效果很好!

现在的问题:当数据库中的值被其他应用程序在 firebase db 中更新时,我们得到 snapshow 并且 _propertyValue 的值正在被正确更新,但由于 configuration 属性未更改,因此不会触发 changeDetection直接。

所以我需要手动完成。我正在考虑从装饰器中的函数触发更改检测器,但我不确定如何将更改检测器的实例传递给装饰器。

我有一个解决方法:在app.component 的构造函数中,我保存了对全局窗口对象中更改检测器实例的引用:

constructor(cd: ChangeDetectorRef) {
    window['cd'] = cd;

    (...)
}

现在我可以像这样在AutoSync 装饰器中使用它:

// listen for data from db
_ref.on('value', snapshot => {
    _propertyValue = snapshot.val();
    window['cd'].detectChanges();
});

但这是一种老套且肮脏的解决方案。什么是正确的方法?

【问题讨论】:

  • 这主要是 TypeScript 问题。

标签: angular typescript dependency-injection angular2-changedetection angular-decorator


【解决方案1】:

没有将类属性值传递给装饰器的好方法。将提供程序引用保存到全局变量不仅是hacky而且是错误的解决方案,因为可能有多个类实例,因此可能存在多个提供程序实例。

属性装饰器在类定义中评估一次,其中target 是类prototype,并且预计propertyKey 属性在那里定义。 _ref_propertyValue 对于所有实例都是相同的,即使组件从未实例化,_ref.on 也会被调用和侦听。

一种解决方法是在类实例上公开所需的提供程序实例 - 或 injector 如果应该在装饰器中访问多个提供程序。由于每个组件实例都应该设置自己的_ref.on监听器,所以应该在类构造函数或ngOnInit钩子中执行。无法从属性装饰器中修补构造函数,但应该修补 ngOnInit

export interface IChangeDetector {
  cdRef: ChangeDetectorRef;
}

export function AutoSync(firebasePath: string) {
  return (target: IChangeDetector, propertyKey: string) => {
    // same for all class instances
    const initialValue = target[propertyKey];

    const _cbKey = '_autosync_' + propertyKey + '_callback';
    const _refKey = '_autosync_' + propertyKey + '_ref';
    const _flagInitKey = '_autosync_' + propertyKey + '_flagInit';
    const _flagDestroyKey = '_autosync_' + propertyKey + '_flagDestroy';
    const _propKey = '_autosync_' + propertyKey;

    let ngOnInitOriginal = target['ngOnInit'];
    let ngOnDestroyOriginal = target['ngOnDestroy']

    target['ngOnInit'] = function () {
      if (!this[_flagInitKey]) {
        // wasn't patched for this key yet
        this[_flagInitKey] = true;

        this[_cbKey] = (snapshot) => {
          this[_propKey] = snapshot.val();
        };

        this[_refKey] = firebase.database().ref(firebasePath);
        this[_refKey].on('value', this[_cbKey]);
      }

      if (ngOnInitOriginal)
        return ngOnInitOriginal.call(this);
    };

    target['ngOnDestroy'] = function () {
      if (!this[_flagDestroyKey]) {
        this[_flagDestroyKey] = true;
        this[_refKey].off('value', this[_cbKey]);
      }

      if (ngOnDestroyOriginal)
        return ngOnDestroyOriginal.call(this);
    };

    Object.defineProperty(target, propertyKey, {
      get() {
        return (_propKey in this) ? this[_propKey] : initialValue;
      },
      set(v) {
        this[_propKey] = v;
        this[_refKey].set(v);
        this.cdRef.detectChanges();
      },
    });

  }
}

class FooComponent implements IChangeDetector {
  @AutoSync('/config') configuration;

  constructor(public cdRef: ChangeDetectorRef) {}
}

这被认为是黑客攻击

【讨论】:

  • 我明白了。由于所描述的限制,没有好的方法可以做到这一点。我更新了答案,但是设置其他属性而不是 prop 装饰器所属的属性被认为是 hack。
  • 我认为您的第一个分析器更好,并且经过一些修改后效果很好!谢谢!
  • 一个更复杂但正确的解决方案是将除 Object.defineProperty 之外的所有内容移动到父类并从中继承。所以组件是class Foo extends Autosyncable。而且只有 Autosyncable 类可以有 @Autosync 装饰器。
  • 我再次查看了您的解决方案!效果很好!我注意到几个错误:1. 在ngOnInit 中,您应该检查if (!this[_refKey]),2. 在ngOnDestroy 中,您应该检查if (this[_refKey]),3. 在target[_cbKey] 函数中,您应该为target[_propKey] 赋值。此外它工作得很好!还有一件事:如何获得对重新定义对象的引用(我想在这里使用代理)?
  • 是的,这发生在重构期间。我已经更新了代码,现在应该可以了。那么这里需要额外的标志道具,因为在这种情况下很常见。
猜你喜欢
  • 2017-11-12
  • 1970-01-01
  • 2020-05-11
  • 1970-01-01
  • 2021-12-23
  • 2021-11-15
  • 2014-11-17
相关资源
最近更新 更多