簡體   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