3

I am integrating or sort of rewriting my Angular 1 application with Redux. It has the following architecture:

app1 => App1 running on diff subdomain app2 => App2 running on diff subdomain and so on... shared => Shared modules(with multiple components) b/w app1 and app2

An app is made of multiple modules specific to it.
Shared directory has one or many modules that are shared between two or more apps

So my question is should I have app level store or a module level store.

By app level store and module level store I mean, for example:

  1. Let's say we have a shared module SM1 which is shared between two apps.
  2. SM1 should use the store of app1 when used inside app1 and should use the store of app2 when used inside app2. But a module should be a self-sustained entity and it should not rely on any other thing for its store.
  3. Or the SM1 could have its own store not sharing with any app. But when the SM1 is used inside app1 will its store conflict with the store of app1.
  4. Also if I use a module level store then the module and the app both need to be provided with the ngRedux dependencies which seem kind of redundant.

What is the best solution here in terms of a scalable architecture and redux core principles?

Rajat Vijay
  • 102
  • 1
  • 9

2 Answers2

1

redux-subspace was created for a very similar use case (in fact, we have also been migrating 2 seperate apps from angular 1 to react/redux with shared components between them). It allows you to have a single store and isolate a section of it for your shared components.

The basic concept is that the parent app combine's the component's reducer into it's store and then sub stores are created that return a reduced state tree and namespace dispatched actions.

Note: I have not use angular w/ redux and I have no idea how you integrate the two, so I'm only going to show how you can create the sub-stores and hope it works for you (if you do get it working, I'd love to know so we can expand our supported frameworks in our docs).

import { createStore, combineReducers } from 'redux'
import { subspace, namespaced } from 'redux-subspace'

const component1 = (state = { value: 1 }, action) => {
    switch (action.type) {
        case 'INCREMENT':
            return { ...state, value: state.value + 1 }
        default:
            return state
    }
}

const component2 = (state = { value: 10 }, action) => {
    switch (action.type) {
        case 'DECREMENT':
            return { ...state, value: state.value - 1 }
        default:
            return state
    }
}

const reducer = combineReducers({
    component1: namespaced('component1')(component1),
    component2: namespaced('component2')(component2)
})

const store = createStore(reducer)

const component1Store = subspace((state) => state.subApp1, 'subApp1')(store)
const component2Store = subspace((state) => state.subApp2, 'subApp2')(store)

console.log('store state:', store.getState()) // { "component1": { "value": 1 }, "component2": { "value": 10 } }
console.log('component1Store state:', component1Store.getState()) // { "value": 1 }
console.log('component2Store state:', component2Store.getState()) // { "value": 10 }

Now dipatching actions into those sub-stores will also only affect their state

component1Store.dispatch({ type: 'INCREMENT'})

console.log('store state:', store.getState()) // { "component1": { "value": 2 }, "component2": { "value": 10 } }
console.log('component1Store state:', component1Store.getState()) // { "value": 2 }
console.log('component2Store state:', component2Store.getState()) // { "value": 10 }

component2Store.dispatch({ type: 'INCREMENT'})

console.log('store state:', store.getState()) // { "component1": { "value": 2 }, "component2": { "value": 10 } }
console.log('component1Store state:', component1Store.getState()) // { "value": 2 }
console.log('component2Store state:', component2Store.getState()) // { "value": 10 }

Notice that the INCREMENT action dipatched into the component2Store did not change the state of component1Store. The situation is reversed if we dispatch a DECREMENT action

component1Store.dispatch({ type: 'DECREMENT'})

console.log('store state:', store.getState()) // { "component1": { "value": 2 }, "component2": { "value": 10 } }
console.log('component1Store state:', component1Store.getState()) // { "value": 2 }
console.log('component2Store state:', component2Store.getState()) // { "value": 10 }

component2Store.dispatch({ type: 'DECREMENT'})

console.log('store state:', store.getState()) // { "component1": { "value": 2 }, "component2": { "value": 9 } }
console.log('component1Store state:', component1Store.getState()) // { "value": 2 }
console.log('component2Store state:', component2Store.getState()) // { "value": 9 }

At this point, you would need to work out how to inject these sub-stores into your shared components.

Hope this is useful for you.

Disclaimer: I am the author of redux-subspace

Michael Peyper
  • 6,814
  • 2
  • 27
  • 44
  • But if I map my store to somewhat exactly replicate my backend DB, then there should not be any clashes in items in the store. In such case why would one use redux-subspace? – Rajat Vijay Sep 25 '17 at 20:10
  • It may or may not be for you (no hard feelings if not). The main advantage of redux-subspace is that your `SM1` store has a known starting point for it's state, so `app1` and `app2` can combine the `SM1` reducer anywhere they want into theirs (e.g. different levels of nesting, different keys, etc.). The other advantage is the actions from `SM1` get namespaces, so they won't be any cross-talk with other sub-modules using actions with the same action types. This isolation makes future maintenance easier as each app evolves and more and more sub-modules (or even sub-sub-modules) are added. – Michael Peyper Sep 26 '17 at 10:14
1

I would go for loose coupling as much as you can:

  • app1 - own store (integrates sm store)
  • app2 - own store (integrates sm store)
  • sm - own store
Alexander
  • 3,019
  • 1
  • 20
  • 24