如何将 changeDetectorRef 传递给装饰器?
How to pass changeDetectorRef to decorator?
我正在为 属性 创建自己的装饰器,它会自动将 属性 的值与 firebase 数据库同步。我的装饰器非常简单,看起来像这样:
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 对象同步。 属性 setter 自动更新数据库中的值 - 效果很好!
现在的问题是:当其他应用程序在 firebase db 中更新数据库中的值时,我们得到的快照显示和 _propertyValue
的值正在正确更新,但未触发 changeDetection 作为 configuration
属性不直接改
所以我需要手动完成。我正在考虑从装饰器中的函数触发变化检测器,但我不确定如何将变化检测器的实例传递给装饰器。
我有一个解决方法:在 app.component
的构造函数中,我将对更改检测器实例的引用保存在全局 window 对象中:
constructor(cd: ChangeDetectorRef) {
window['cd'] = cd;
(...)
}
现在我可以像这样在我的 AutoSync
装饰器中使用它:
// listen for data from db
_ref.on('value', snapshot => {
_propertyValue = snapshot.val();
window['cd'].detectChanges();
});
但这是一种老掉牙和肮脏的解决方案。什么是正确的方法?
没有好的方法可以将 class 属性 值传递给装饰器。保存提供者对全局变量的引用不仅是一种骇人听闻的做法,而且是错误的解决方案,因为可能有多个 class 实例,因此也有多个提供者实例。
属性 装饰器在 class 定义上计算一次,其中 target
是 class prototype
,预计 propertyKey
属性 在那里定义。 _ref
和 _propertyValue
对于所有实例都是相同的,即使从未实例化组件,也会调用和侦听 _ref.on
。
一种解决方法是在 class 实例上公开所需的提供程序实例 - 或者 injector
如果应该在装饰器中访问多个提供程序。由于每个组件实例都应设置自己的 _ref.on
侦听器,因此应在 class 构造函数或 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) {}
}
它被认为是黑客攻击
我正在为 属性 创建自己的装饰器,它会自动将 属性 的值与 firebase 数据库同步。我的装饰器非常简单,看起来像这样:
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 对象同步。 属性 setter 自动更新数据库中的值 - 效果很好!
现在的问题是:当其他应用程序在 firebase db 中更新数据库中的值时,我们得到的快照显示和 _propertyValue
的值正在正确更新,但未触发 changeDetection 作为 configuration
属性不直接改
所以我需要手动完成。我正在考虑从装饰器中的函数触发变化检测器,但我不确定如何将变化检测器的实例传递给装饰器。
我有一个解决方法:在 app.component
的构造函数中,我将对更改检测器实例的引用保存在全局 window 对象中:
constructor(cd: ChangeDetectorRef) {
window['cd'] = cd;
(...)
}
现在我可以像这样在我的 AutoSync
装饰器中使用它:
// listen for data from db
_ref.on('value', snapshot => {
_propertyValue = snapshot.val();
window['cd'].detectChanges();
});
但这是一种老掉牙和肮脏的解决方案。什么是正确的方法?
没有好的方法可以将 class 属性 值传递给装饰器。保存提供者对全局变量的引用不仅是一种骇人听闻的做法,而且是错误的解决方案,因为可能有多个 class 实例,因此也有多个提供者实例。
属性 装饰器在 class 定义上计算一次,其中 target
是 class prototype
,预计 propertyKey
属性 在那里定义。 _ref
和 _propertyValue
对于所有实例都是相同的,即使从未实例化组件,也会调用和侦听 _ref.on
。
一种解决方法是在 class 实例上公开所需的提供程序实例 - 或者 injector
如果应该在装饰器中访问多个提供程序。由于每个组件实例都应设置自己的 _ref.on
侦听器,因此应在 class 构造函数或 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) {}
}
它被认为是黑客攻击