Angular 2 绑定到计算的 getter 会导致调试错误

Posted

技术标签:

【中文标题】Angular 2 绑定到计算的 getter 会导致调试错误【英文标题】:Angular 2 bind to computed getter gives debug erros 【发布时间】:2016-08-12 17:21:26 【问题描述】:

我正在使用 Angular 2 和 lodash。

我有一个带关系的模型,我有一个像这样的吸气剂:

get relationsPerType() 
    return _(this.Relations)
        .groupBy(p => p.Type)
        .toPairs()
        .map(p => (
            type: <MessageRelationType>p[0],
            relations: <Relation[]>p[1]
        ));

将relationshipPerType 的值绑定到*ngFor 指令

编辑:这是我的绑定:

<div *ngFor="#relationGroup of Message.relationsPerType">
    ...
</div>

给我以下错误:

表达式“MessagesDetailsComponent@60:8 中的 Message.relationsPerType” 检查后有变化。上一页

这似乎是完全正确的,因为确实每次调用它时都会计算它。

无论如何,拥有像这样的“计算”变量,我无法想象 Angular 2 更改检测如何检测到 RelationsPerType 实际发生了变化。 比如将 getter 标记为不可变?

我想更好的方法是:

a) 从一开始就将计算得到的 getter 值存储在属性中

b) 使父对象不可变,以便 Angular 不跟踪属性的更改

有没有更好的方法来做到这一点?

【问题讨论】:

【参考方案1】:

您应该缓存该值并绑定到缓存的值,或者创建一个可在数据更改时发出新事件的 observable。

如果您绑定到relationsPerType,则每次 Angular 检查是否发生更改时都会创建一个新集合,并且 Angular 会将此视为更改,因为它会获取两个不同的实例,即使它们可能包含相同的数据。

calcRelationsPerType() 
    this relationsPerType = _(this.Relations)
        .groupBy(p => p.Type)
        .toPairs()
        .map(p => (
            type: <MessageRelationType>p[0],
            relations: <Relation[]>p[1]
        ));

然后像这样的绑定应该可以正常工作:

<div *ngFor="#relationGroup of Message.relationsPerType">
    ...
</div>

【讨论】:

嘿,君特!我认为您的第一个 sn-p 中有一个错字:this relationsPerType = return _(this.Relations) ;-) 感谢君特。您所说的实际上是“a)从一开始就将计算得到的 getter 值存储在属性中”,在数据绑定之前调用 calcRelationsPerType() 需要付出更多努力。 很难判断您在问题中提供的信息是否付出了很多努力。您需要找到一种方法来在结果取决于值更改时重新计算它。恕我直言,最容易理解的解决方案是我在回答中显示的内容。更高级的是使用 observables。您的装饰器方法也很有趣,但我认为使缓存无效与调用重新计算非常相似。 君特,你说得对,让缓存失效。 Observables 也是一个不错的选择。【参考方案2】:

经过一番挖掘,我发现使用 decorators 的方法比 a) 和 b) 提供的更好,因为:

a) 我不想放弃 getter 函数提供的“惰性”计算并将所有内容都变成属性

b) 不可变是一种可行的解决方案,但不适用于我的情况

因此,来自 C# 和大量面向方面的编程(参见 PostSharp),我终于设法创建了一个缓存属性 getter 装饰器 函数,每个对象只评估一次:

function cachedProperty(target: any, key: string, descriptor: PropertyDescriptor) 
    let originalGetter = descriptor.get;
    descriptor.get = function () 
        let cachSetted = this[`__cachedsetted$key`];
        if (typeof cachSetted !== 'undefined') 
            return this[`__cached$key`];
        
        this[`__cachedsetted$key`] = true;

        return this[`__cached$key`] = originalGetter.call(this);
    

之后所有需要改变的就是用@cachedProperty 装饰器装饰getter,如下所示:

    @cachedProperty
    get relationsPerType() 
        return _(this.Relations)
            .groupBy(p => p.Type)
            .toPairs()
            .map(p => (
                type: <MessageRelationType>p[0],
                relations: <Relation[]>p[1]
            )).value();
    

使用此装饰器,对象只会更改一次,因此 Angular 2 依赖注入不会抱怨。此外,我不会放弃“惰性”评估,也不会添加更改架构的辅助属性。

如果缓存变得陈旧,当然必须处理他想要使缓存失效的情况。这将需要删除

`__cachedsetted$key`

来自此的属性。

【讨论】:

以上是关于Angular 2 绑定到计算的 getter 会导致调试错误的主要内容,如果未能解决你的问题,请参考以下文章

Angular @Input getter/setter 和非原始值

Angular 2 Setter 和 Getter

由Vue引发的getter和setter思考

Vue引发的getter和setter

angular和vue区别

Vue响应式原理/双向数据绑定