0

My normalized ngrx store looks like this:

export interface State {
    carts: EntityState<Cart>;
    items: EntityState<Item>;
}

export interface Cart {
    id: number;
    maxVolume: number;
}

export interface Item {
    id: number;
    cartId: number;
    volume: number;
}

It's a pretty basic setup where a cart can contain several items. My selector needs to return an array with all the carts with arrays containing their items, but also compute if items are in danger of dropping out of their respective cart:

export const select: MemoizedSelector<object, any> = createSelector(
  selectAllCarts, selectAllItems, 
  (allCarts: Cart[], allItems: Item[]) => {
    return allCarts.map(c => {
      const items = allItems.filter(i => i.cartId == i.id);
      return {
        id: c.id,
        items: items.map(i => {
          // computations, should not run if cart's items have not changed
          // needs to be memoized
          const computed = isCartOverfilled(i, c, items);
          return {
            id: i.id,
            mightFallOut: computed//computed value needs to be output
          }
        })
      }
    });
});

Every time an item updates, isCartOverfilled will run for each item in the store. But, isCartOverfilled is potentially expensive and depends only on the items inside a cart. When an item updates, e.g. gets added to a cart, isCartOverfilled should execute ONLY for the items inside, i.e. memoized by cart id.

How do I achieve this?

I have tried selecting items from a single cart:

export const selectCart = (cartId: number) => createSelector(
  selectItemsByCartId(cartId), selectCartById(cartId),
  (items: Item[], cart: Cart) => {
  return {
    id: cart.id,
    items: items.map(i => {
      const computed = isCartOverfilled(i, cart, items);
      return {
        id: i.id,
        mightFallOut: computed
      }
    })
  }
});

This selector would not excessively compute, but I need all the carts, and I'm not sure if it's doable with a selector.

Ante Novokmet
  • 388
  • 3
  • 13

1 Answers1

0

As the state is already changed the selector will recalculate.

But you can achieve using keeping another state for cart and when you dispatch addCart Item action you can update the cart with single item instead of using selector.

export interface State {
    carts: EntityState<Cart>;
    cartOverFilled: SomeState or part of Cart State <---
    items: EntityState<Item>;
}

or

export interface State {
    carts: EntityState<CartState>;
    items: EntityState<Item>;
}

export interface CartState {
    cart: Cart;
    overFilled: any
}

export interface Cart {
    id: number;
    maxVolume: number;
}
  1. Add one more state with values to check overfilled;
  2. Create an effect for ofType 'Add to cart '
  3. Create another action to recalculate the with existing state of overfilled with incoming item.
rijin
  • 1,709
  • 12
  • 21
  • I am aware of the first point. As for the second, are you suggesting I move computation to the reducer and actually store the computed state to store? Is there a way to create such a selector that when it recalculates, only recomputes items from a single cart? – Ante Novokmet Jan 03 '19 at 16:43
  • Dispatch another action – rijin Jan 03 '19 at 16:45