6

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.

Motivation to use Redux(NGRX)

  1. We had data that we needed to share between multiple components. Sharing through properties was not possible since we had components separated by Angular router.
  2. Reduce number of requests by reusing loaded data like Auth/Session
  3. Performance - Angular performs considerably faster when the component is using ChangeDetectionStrategy.OnPush

Business problem

Inside our application, we have 6 modules. Each module has at least 10 features(pages). We have just few common details shared between them.

First steps with Redux Store

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.

Migrating modules features

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": {}
}

Problems

Action type collision

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.

Big store

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.

Solutions which we considered

Lazy loading

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.

Switching to entity reducers

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.

Don't move features to Redux but keep them in containers

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.

Reset stores

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());
  }
Maxian Nicu
  • 2,166
  • 3
  • 17
  • 31

1 Answers1

1

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

Jesse
  • 2,391
  • 14
  • 15