We're developing SPA using Angular 7 in a combination with NGRX. We have migrated to NGRX a few months ago to use mainly for authentication and session data. After that, we have started moving other functionalities as well. I will describe the problem and what solutions we have in our heads and their drawbacks. I would be very thankful to hear how you solved this problem(if you had) or ideas on how to solve having problems.
ChangeDetectionStrategy.OnPush
Inside our application, we have 6 modules. Each module has at least 10 features(pages). We have just few common details shared between them.
Since our main problem was #1 and #2(described above), we started with following store structure:
{
"authentication": {
"isLoading": false,
"error": {},
"token": "auth-token"
},
"session": {
"userDetailsIsLoading": false,
"userDetailsData": {},
"userDetailsError": {}
}
}
So far, so good. It was simply like all Redux Tutorials with increase/decrease/reset counter.
Here the fun began since we have a lot of features inside app (6 modules * 10 features = 60+).
The first decision was to use reducer per feature
and not reducer per entity
because we have pages with the same type of entity, but fetched with different filters. We're filtering data on the server side, because of a big amount of data.
Ex: Recent 10 posts, Top 10 posts - both on the same page
So, we have extended our store with more reducers:
{
"authentication": {},
"session": {},
"blogs-list": {},
"blogs-add": {},
"administration-users": {},
"administration-add-user": {}
}
Yes, we had first type collision as we were using format [Feature] Action Description
.
To solve it, we have decided to add use a namespace [Module][Feature] Action Description
.
That's the biggest problem. The entire store becomes very big. It's not a problem when the store is clean, but after a few navigations, the store contains a lot of unneeded data.
Angular allows to lazy-load so-called NGRX Features
. This means we can start with just shared data(auth, session) and once a module is opened, all reducers are added to the store. This solution solves the problem partially. If you navigate to all modules, the store becomes big again.
This looks easy in theory, but hard in practice. As I mentioned, we have a kind of dashboard where we display the same entities but fetched with different filters + pagination. In the head I have an idea to have different reducers for these cases, like:
{
"latest-entities": {
"isLoading": bool,
"data": {},
"error": {},
},
"most-popular-entities": {
"isLoading": bool,
"data": {},
"error": {},
}
}
but there we have another drawback. We used to keep filters in feature-store and once we change filters, effects are triggering data reload. Switching to this architecture, we move the responsibility of data reloads to the containers
. Container
will be responsible for dispatching actions to both stores latest-entities
and most-popular-entities
. This solution will also get bigger and bigger. The problem couldn't be fully solved by reorganizing reducers and store.
We can keep feature logic in containers
and use redux only for cross-module data like a session, auth, and notifications. This is also a solution but has its drawbacks. I would like to keep the application consistent and not to have business logic in both containers and store.
To keep the store smaller, we can create an action that will reset the store to the initial state which will be dispatched once the feature is not used. Here is an example:
public ngOnDestroy(): void {
this.store$.dispatch(new BlogStoreActions.DestroyAction());
}
As an app gets large and complex, you might want to store large, bulky data inside your Redux store and access it inside a component. A Redux store doesn't have a limit on the amount of data stored, so you can pretty much use it to store almost anything, including bulky JSON data, data in table form, etc.
There is a central store that holds the entire state of the application. Each component can access the stored state without having to send down props from one component to another. There are three core components in Redux — actions, store, and reducers.
This Data can be about 70-80 KB but I think average size of each user will be 30-40 kb. This data is modified with combined of 5-6 reducers and 30-50 actions. I have one component which uses all of this data and 10-15 components which uses some part of it.
There is no “right” answer for this. Some users prefer to keep every single piece of data in Redux, to maintain a fully serializable and controlled version of their application at all times. Others prefer to keep non-critical or UI state, such as “is this dropdown currently open”, inside a component's internal state.
I can't comment yet, but your question had me curious so I posed it to twitter and one of the ngrx maintainers.
Here was the response (from Wes Grimes):
If the store gets too big then maybe it’s time to reconsider what data is fetched client side. Do you really the full data model or would a paired down view model suffice? Fetch data in batches bits at a time. Get 50 and then go back for 50 more as you need it (lazy load).
Also take advantage of lazy loaded feature modules in angular and use the forFeature with NgRx and then only load the portion of the store needed. But again. If size is a concern, consider minimizing client side data and offload the heavy lifting to the backend.
Link:
https://twitter.com/qkwrv/status/1217593781282734080
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With