EntityAdapter selectAll ordering stability when sortComparer omitted and sort-relevant properties updated
0 reputation · 22 Nov 2023, 14:56 UTC
0 reputation · 22 Nov 2023, 14:56 UTC
Achieve predictable display order for entity lists derived from selectAll when entities are frequently updated on properties that should affect sort position.
The EntityAdapter only reorders the internal ids array when a sortComparer is provided and an operation mutates that array (add, upsert, remove). An update() that changes a sort-relevant field leaves the ids array reference unchanged, so the memoized selectAll selector returns the same array reference and Angular change detection sees no difference. Without a sortComparer, order reflects insertion sequence, which can diverge between eagerly and lazily loaded feature modules. In ngrx v16+ signal-based stores, the interaction between selector memoization and signal equality checks adds another layer of uncertainty about when a new array reference is produced.
selectAll output after a sort-relevant property update without re-adding every affected entity?entityAdapter API expose a built-in mechanism to invalidate the ids ordering on property changes, or must developers dispatch a synthetic action that replaces the ids array?createSelector behavior when the underlying ids array reference is preserved but entity property values change?35775 reputation · 22 Nov 2023, 15:57 UTC
EntityAdapter does not re-sort the internal ids array when updateOne or updateMany changes a sort-relevant property, even when a sortComparer is configured. The comparer runs only while the ids array is mutated by add, upsert, remove, or setAll. Because selectAll reads from ids, the displayed order remains stale after a property update unless you sort at read time or explicitly replace ids in state.
Confirmed: updateOne updates the entity map and returns the existing ids array reference unchanged. selectAll maps over ids, so it cannot surface a new position for the changed entity.
Likely explanation: Without sortComparer, ids reflects insertion order. Eager and lazy feature modules may upsert entities in different sequences, so the same collection can have different ids sequences depending on load timing. In signal-based stores (NgRx v16+), selectAll emits a new array reference when the entities signal changes, but the order inside that array still follows the unsorted ids sequence.
Do not re-add entities. Derive a sorted collection from selectAll so state stays untouched and every property update is reflected immediately.
import { createSelector } from '@ngrx/store';
export const selectSortedEntities = createSelector(
selectAll,
(entities) => [...entities].sort((a, b) => a.updatedAt - b.updatedAt)
);
import { computed } from '@angular/core';
const sortedEntities = computed(() =>
selectAll().slice().sort((a, b) => a.updatedAt - b.updatedAt)
);
Bind the derived collection in your template. This avoids mutating ids on every property change and prevents excessive re-renders.
If other selectors or persistence depend on the stored ids order, dispatch a synthetic reSort action after batches of updates, not after every single update.
adapter.setAll(selectAll(state).slice().sort(sortFn), state);
patchState(store, { ids: [...ids()].sort(sortFn) });
The functional createEntityAdapter exposes no reSort, invalidateSort, or automatic property-watch trigger. You must either sort at read time or replace ids explicitly through an action.
Then apply the derived selector or computed signal above and confirm the logged output reflects the new sort position immediately.
Are you using the classic Store with createSelector, or the signal-based signalStore with withEntities? The read-time sorting pattern works for both, but the implementation differs: classic uses createSelector, while signal stores use computed over selectAll. Knowing your runtime determines which snippet applies.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.