繁体   English   中英

角度异步管道不更新视图

[英]angular async pipe not updating the view

我的问题可以通过与ngrx文档的选择器示例进行类比来最好地描述,以使事情保持简单( https://github.com/ngrx/platform/blob/master/docs/store/selectors.md#using-selectors-for多件状态 )。

我使用异步管道来订阅某些状态切片,例如使用选择器进行选择

this.visibleBooks$ = this.store$.select(selectVisibleBooks)

问题是,如果allBooks数组是“ small”(小于100个项目),则我的视图会立即更新。 但是,当它大于100时,我的视图仅在下次触发更改检测(例如通过滚动)时才更新。 这是非常糟糕的用户体验,仅在滚动列表后才能看到书籍。

我看了异步管道的源代码( https://github.com/angular/angular/blob/master/packages/common/src/pipes/async_pipe.ts ),实际上_updateLatestValue方法调用_updateLatestValue ChangeDetectorRef.markForCheck() ,据我所知,它会在下次触发更改检测将要检查的组件标记为更改。

我目前的解决方法是在顶级组件中手动订阅

this.store$.select(selectVisibleBooks).subscribe(cb)

并在回调中手动调用ChangeDetectorRef.detectChanges()

我发现这并不令人满意,并且无论Book[]数组有多大,都希望异步管道始终运行。 有人对我可以做一些建议或纠正吗?


根据要求编辑

如上所述,上面的“书店”案例只是我为简化程序而编写的应用程序的类比。 实际上,我的应用程序渲染图的节点和边缘,其中节点和边缘还附加有一个称为“ vnode”的版本,该版本与“ vedge”一起跨越了一个版本树。 因此,任何图元素都有其自己的版本树。

我当前正在开发的是一个搜索表单,我们在其中向后端发送特定请求,以向其询问与一组特定搜索键/值对匹配的任何节点。

因此,这些节点将随后呈现在组件<nodes-list> ,我们通过输入绑定传递节点

<nodes-list [nodes]="nodes$ | async"></nodes-list>

nodes-list具有“按入”更改检测功能,而顶级<search>组件具有默认策略。

ngOnInit()中将nodes$设置为

this.nodes$ = this.store$.select(selectFullNodesList)

selectFullNodesList看起来像这样:

export const fullNodesSelector = getFullNodesSelector(createSelector(selectSearchState, s => {
    if (s.currentId) {
        const nodes = s.queries.get(s.currentId).nodes;
        if (nodes) {
            return [...nodes];
        }
    }
    return null;
}))

export const selectFullNodesList = createSelector(
    fullNodesSelector,
    (global: GlobalState) => global.data.counts,
    createSelector(selectSearchState, s => s.sort),
    (nodes, counts, sorting) => {
        if (!nodes || !counts || !sorting) return null;
        return [...nodes.sort(sorting.sortCbFactory(counts))];
    }
)

让我解释:

  • getFullNodesSelector(...)我将在下面显示,它位于顶级库中,因为我们可能会在许多功能中重复使用它。 但是它的作用是,它以另一个选择器作为参数,该选择器指向节点和vnode密钥对{key: number, vKey: number}[]数组,并将该数组转换为附加了vnode的节点数组(请参阅下面的方法)。
  • 因此,您可以看到,传递给它的选择器选择了search功能的状态,如果存在currentId ,这是当前请求到后端的ID,那么我们选择作为当前请求结果的节点。
  • s.queries是一个围绕Javascript对象的轻量包装程序,它使我可以轻松获取/设置值,克隆或向克隆添加新项目。 当在NGRX中使用键/值存储时,这对我很有帮助。 因此, s.queries.get(s.currentId).nodes
  • global.data.counts只是每个节点有多少个邻居的列表。 我想知道这一点,因为我想按“计数”对节点列表进行排序。
  • s.sort是当前选择列表的排序方式。
  • 请注意,使用sortCbFactory ,该工厂仅返回正确的回调以传递给Array.sort ,但我需要在回调的本地范围内显示counts ,因为否则我将无法按计数进行排序。
  • 因此,每当节点发生更改(例如,在该节点上引用了新版本),计数发生更改(邻居添加到节点)或排序发生更改时,都会调用投影函数,并发出新的节点列表。
  • 请注意,我们在排序后返回了一个新的数组。

selectSearchState只是功能选择器

export const selectSearchState = createFeatureSelector<SearchState>('search');

getFullNodesSelector(...)看起来像这样:

function getFullNodesSelector(keyPairsSelector: MemoizedSelector<object, GraphElementKeyPair[]>): MemoizedSelector<object, INodeJSON<IVNodeJSON>[]> {
    return createSelector(
        keyPairsSelector,
        (s: GlobalState) => s.data.nodes,
        (s: GlobalState) => s.data.vnodes,
        (pairs, nodes, vnodes) => {
            if (!pairs || !nodes || !vnodes) return null;
            return pairs.map(pair => ({
                ...nodes.get(pair.key),
                _SUB: {
                    ...vnodes.get(pair.vKey)
                }
            }));
        })
}

再次发表一些评论:

  • 如您所见,我们传递了一个选择器,该选择器指向GraphElementKeyPair{key: number, vKey: number} )数组
  • 我们询问节点存储和vnodes存储的全局状态
  • 我们将所有对映射到一个新对象。
  • 请注意,节点和边缘仍然是前面提到的包装器对象,它具有get方法。

因此,由于我们已经使用异步管道订阅了this.nodes$ ,因此每次流<nodes-list>上有一个新事件都应进行更新。 但是,实际上,这似乎取决于INodeJSON<IVNodeJSON>[]的大小,并且如果数组的长度> INodeJSON<IVNodeJSON>[] ,我们必须通过单击某个地方或滚动来手动触发更改检测。 对于较小的数组,应该自动刷新节点列表。

大型数据集不会有任何问题。 您的选择器应该是同步的。 这意味着选择器在运行时,在后台没有其他任何事情发生。 不管在选择器中计算所有内容需要花费多少时间,都可以。 如果花费的时间太长,您的浏览器可能会死机,仅此而已。

当你说

我使用了ngrx-store-freeze,它应该防止出现这种情况

It is not true.

从商店的角度来看,这是事实。 但是,让我们想象一下:

您的商店有一个ID数组(比方说用户ID)。

您有一个名为getAllUsers的第一个选择器。
这只是映射用户ID并检索正确的用户,对吗? 签名为(usersIds: string[]): User[]

当然,在这里,您将创建一个新的数组引用,并且(不应该)使usersIds数组发生突变。

但是,然后,您有了另一个选择器。 getUsersResolved基本上可以“解析”外部属性。 假设用户有动物列表。 从第一个选择器中,您将获得一个用户列表,每个用户都有一个animalsIds属性。 但是,您想要的是改为具有animals数组。 如果从该选择器中ngrx-store-freeze了原始数组(来自第一个选择器的数组),则ngrx-store-freeze将不会引发任何错误,这是有道理的:您正在更改一个数组,但不是存储中的一个数组。

那怎么可能是个问题呢?

  • 您的组件订阅getUsersResolved ,将其分配给一个变量,然后使用async管道从视图中订阅该变量(假设这是您第一次在整个应用程序中订阅它!)
  • 然后,第一个选择器getAllUsersgetUsersResolved调用(首次)(也首次调用)
  • getAllUsers按预期创建一个新数组,并将其传递给getUsersResolved 因为这是第一次,即使您将该数组修改为getUsersResolved ,您也不会有任何问题:更改检测将在第一次接收该数组时完成
  • 现在,假设您的用户列表**不会*更改,但是动物列表会更改。 您的选择器getUsersResolved将被触发,但是如果您不尊重不变性并修改来自getAllUsers的第一个数组,则该数组引用不会更改,并且更改检测也不会发生。 而且,因为该数组不是商店的一部分,这是一个很好的选择,它是由选择器创建的数组

因此,我不确定您的问题是否来自那里,但是您可能要仔细检查一下您是否尊重选择器中的不变性。

最终,如果不确定,请共享selectVisibleBooks的代码, 以及它使用的每个选择器

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM