Hey I can give my opin…

4 minute read

Hey ! I can give my opinion on this if you like. I think of it slightly different. I would say the [Search Page] Add item to cart action would be different from the [Cart] Add item to cart action. The first is a feature based action or event (you clicked the button) on that page. Whereas the latter action is a data-access action that would have slightly different responsibility. For example, a feature action may update some UI state or optimistically increase the items in cart UI number. The data-access action, would actually make the back-end call to set the item in the cart on the backend

Responses:

Oops, sorry I managed to slightly mess up my example (but you brought something up that I’ll also mention in the end). I meant to write

> [Cart] Add item to cart becomes [Search Page] Add item to cart and [Cart Page] Add item to cart

Basically indicating you added something to your cart on a search page, or on the cart page (maybe by increasing the item count). Perhaps I should have also added [Home Page] Add item to cart (Amazon has things you can add to your cart from the homepage). Meaning, you can add additional items to your cart on all 3 pages.

I imagine these are 3 different features, data-accesss, etc for each of these “areas” (they aren’t separate apps, right?):

libs/
  |_my-cool-store
    |_feature-home/        # smart components here
    |_ui-home/             # presentational components here
    |_data-access-home/    # actions, selectors, store state here (or rather in a nested `+state`)
    |
    |_feature-cart/
    |_ui-cart/
    |_data-access-cart/
    |
    |_feature-search/
    |_ui-search/
    |_data-access-search/

So in this scenario the question is

  • is it right to have [data-access|feature]-[home|search] use selectors from data-access-cart?

They need access to these selectors to show a cart preview (like showing just the number of items e.g. Amazon). Alternatively there could be a shared header of some sort, but it still needs to access data-access-cart.

Additionally, we have three [[Home|Search|Cart] Page] Add item to cart actions that need to result in an item being persisted in the state (and database). To do this, I see the reducer and effects under data-access-cart importing the data-access-[home|search|cart] actions and adding them to the reducer/effects:

// .../data-access-cart/+state/state.reducer.ts
const cartReducer = createReducer(
  initialCartState,
  on(
    homePage.addItemToCart,
    searchPage.addItemToCart,
    cartPage.addItemToCart,
    (state, payload) => cartEntityAdapter.addOne(payload)
  )
)

// Same for the effect to persist said cart in a database
// ...

The home, search, and cart pages can all additionally have their own reducer that changes some ui state specific to them of course.

Now my two questions are

  • is it right to have [data-access|feature]-[home|search] use selectors from data-access-cart?
  • is it right to have data-access-cart use actions from data-access-[home|search]?

Now, this is a bit tangential but I’d like your thoughts related to what you mentioned:

> I would say the [Search Page] Add item to cart action would be different from the [Cart] Add item to cart action. The first is a feature based action or event (you clicked the button) on that page. Whereas the latter action is a data-access action that would have slightly different responsibility. For example, a feature action may update some UI state or optimistically increase the items in cart UI number. The data-access action, would actually make the back-end call to set the item in the cart on the backend

This reminds me that I think that the example directory I gave above is wrong, the ngrx state for the cart page and the actual cart contents are under the same slice of state defined in data-access-cart.

I think this has a few major downsides:

  1. This couples the cart data with the “shape” (or “view model”) the cart page needs. Meaning if Item has properties {x, y, z} but the cart page only needs {x, y}, you’re probably going to hit the endpoint that just returns {x, y}. Now if another page needs {x, y, z} for the preview, this data is no longer reusable and you’ll have to manage a separate Item state somewhere and call more endpoints.
  2. You load all the state for the cart page when you don’t need to access the page, just the preview.

I think the cart page state should be completely separate from the state that manages the contents of the cart. You should have a separate data-access library for each “table” in your database. That way all the entity management/crud is encapsulated in a single module and source of truth and all the consumers of this data can use selectors to mold the data into whatever shape they need. This means the structure would be like this:

libs/
  |_my-cool-store
    |_feature-home/        # smart components here
    |_ui-home/             # presentational components here
    |_data-access-home/    # actions, selectors, store state here (or rather in a nested `+state`)
    |
    |_feature-cart/
    |_ui-cart/
    |_data-access-cart/
    |
    |_data-access-cart-item-repository/ # or "-database", whatever you feel is accurate
    |
    |_feature-search/
    |_ui-search/
    |_data-access-search/

data-access-cart-item-repository will only contain cart contents (aka items). It also contains the services, reducers, and effects needed to update said contents and persist it in state. data-access-cart will only have state, reducers, etc. that relates to the page itself.

This structure still needs answers to the two questions mentioned earlier as the data-access-cart-item-repository reducer looks the same as cartReducer (just renamed to, say, cartItemRepositoryReducer) defined earlier and of course the other libraries will consume selectors from data-access-cart-item-repository

That was tiring hope it made sense. :sweat_smile:

Thanks for your feedback!

Might be easier to read, this has snippet colors:

My philosophy is a data-access lib is not for storing NGRX, but for managing the code to hit data stores ( another API, a database, etc). Hence the name data-access.

If i have some NGRX code that manages feature state then I put that NGRX code in the feature lib

Updated: