angular-redux / ng-redux

Angular bindings for Redux
MIT License
1.16k stars 177 forks source link

Dynamic Load Reducers #121

Closed Negan1911 closed 7 years ago

Negan1911 commented 7 years ago

Hi, i'm from a team of developers that hardly uses angular components, and we don't have a way to share our reducers in the same component level (we have to do a big combine of all reducers), so, taking advantage that the dependency tree runs the .config of every component and in the last time, the root .config, i developed this feature to being able to save your reducers dynamically.

This will help #75 and #80

m3fawner commented 7 years ago

I would really like to see this merged in. In a world of modularity and bundle chunking, this is a feature that becomes more and more of a necessity.

We're attempting to create a store, using its current state as the initial state, while using the newly combined reducers, but it appears that this approach won't work

Negan1911 commented 7 years ago

Until this got merged you could use this https://github.com/Negan1911/ng-redux-injector is just a simple factory that will hold the reducers instances to combine them later :) i hope that this got merged soon

m3fawner commented 7 years ago

Unfortunately, that doesn't solve my issue. Since I still am required to create a new store as a result of the merged in reducer, I have a race case scenario where the object reference change to the store causes the newly fired reducer to drop state.

m3fawner commented 7 years ago

Found a solution that doesn't require a modification to ngRedux.

Ultimately, the createStoreWith takes in a pure function...so the solution? Write a pure function that is dynamic. Here's how I did it:

export default class DynamicReducerLoader {
    createStoreWith(reducers, middlewares, enhancers) {
        if(!this.$ngReduxProvider) {
            throw new Error('DynamicReducerLoader needs to have an $ngReduxProvider assigned to it prior to creating store, please add the provider prior to creating store');
        } else {
            this.currentReducerObject = reducers;
            this.currentReducerFunction = combineReducers(reducers);
            this.$ngReduxProvider.createStoreWith(this.currentReducer.bind(this), middlewares, enhancers);
        }
    }
    currentReducer(state, action) {
        return this.currentReducerFunction(state, action);
    }
    injectReducer(reducer) {
        this.currentReducerObject = Object.assign(this.currentReducerObject, reducer);
        this.currentReducerFunction = combineReducers(this.currentReducerObject);
    }
}

It takes in an object defining the initial reducers (which I then call combine on). The pure function that is passed into the createStoreWith call is that of "currentReducer". This has its internal function replaced dynamically by "injectReducer". So, as reducers are injected, and recombined into a new reducer function, the currentReducer reference does not change, but the behavior of it does.

Negan1911 commented 7 years ago

Awesome!

m3fawner commented 7 years ago

It's not pretty, but it works. I'd prefer that it be built into the provider, as to encapsulate the fact that I require $ngReduxProvider assigned onto the class before it can actually be used. It's yet another layer of abstraction on a layer of abstraction.

AntJanus commented 7 years ago

Hey all,

I actually really like this feature but wouldn't it make more sense to just add a framework-agnostic enhancer?

I created an enhancer called Paradux that does exactly that with vanilla Redux:

function paraduxEnhancer(createStore) {
  return (reducer, initialState, enhancer) => {
   let reducers = [];

   function addReducer(reducerFunc) {
     reducers.push(reducerFunc);
     var unsubscribed = false;

     return function() {
       if (!unsubscribed) {
         reducers.splice(reducers.indexOf(reducerFunc), 1);
         unsubscribed = true;

         return true;
       }

       return false;
     }
   }

   function enhancedReducer(reducer) {
      return (state, action) => {
        var newState = reducers.reduce((tempState, reducerFunc) => {
          return reducerFunc(tempState, action);
        }, reducer(state, action));

        return newState;
      };
   }

   const store = createStore(enhancedReducer(reducer), initialState, enhancer);

   return Object.assign({}, store, { addReducer });
  }
}

I can make it available as an open-source lib and it should solve all the issues without messing with ng-redux directly.

AntJanus commented 7 years ago

I'm going to close this out as it is out of scope for ng-redux.