Jordi
Jordi

Reputation: 23277

Ngrx: meta-reducers and capturing effects

I've added into my IStore a transaction concept. It straightforwardly stands for providing a way to store into my IStore which pending operations keep pending. When they are completed, they are removed:

export interface IStore {
  user: IUser;
  txs: ITxRedux;
}

All my reducers are like:

* reducer name: `'OPERATION'`
* success reducer name: `'OPERATION_SUCCESS'`
* failed reducer name: `'OPERATION_FAILED'`

Some of these reducers (only those need a http request) are captured using @Effects:

@Effect({ dispatch: true })
userLogin$: Observable<Action> = this._actions$
  .ofType('USER_LOGIN')
  .switchMap((action: Action) =>
  {
      ....
  });

Currently, my effects have this pattern:

return make_http_call
  .map(_ => ({type: 'OPERATION_SUCCESS'}, payload: {...}))
  .catch(_ => ({type: 'OPERATION_FAILED'}, payload: {...}));

So, I'd like to get a way by adding or removing a "transaction" into my IStore.txs each time an effect is called or completed. When I say "add a transaction into my IStore.txs" I mean to call transaction reducers:

public static ADD_TX = `ADD_TX`;
private static addTx(txsRdx: ITxRedux, type, payload: ITx) {
    const tx = payload;

    return {
        ids: [ ...txsRdx.ids, tx.id ],
        entities: Object.assign({}, txsRdx.entities, {[tx.id]: tx}),
    };
}

public static REMOVE_TX = `REMOVE_TX`;
private static removeTx(txsRdx: ITxRedux, type, payload) {
    const tx: ITx = payload;
    var entitiesTmp = {...txsRdx.entities};

    delete entitiesTmp[tx.id];
    return {
        ids: txsRdx.ids.filter(id => tx.id != id),
        entities: entitiesTmp
    };
}

I've listen to talk a bit about meta-reducers, but I don't quite whether they are going to be able to get my goal.

Is there any way to get it using a elegant way?

Upvotes: 2

Views: 1409

Answers (1)

superjos
superjos

Reputation: 12735

Late reply, but you might find this post useful. The classic example (taken mostly from that post) is to log each action/state change by means of a logging meta-reducer:

export function logging(reducer) {

  return function loggingReducer(state, action) {
    console.group(action.type);

    // invoke following, "wrapped" reducer in the chain
    const nextState = reducer(state, action);

    console.log(`%c prev state`, `color: #9E9E9E`, state);
    console.log(`%c action`, `color: #03A9F4`, action);
    console.log(`%c next state`, `color: #4CAF50`, nextState);
    console.groupEnd();

    // return wrapped reducer output
    return nextState;
  };

}

In main app module, you compose the new logging reducer factory with the usual combineReducers reducer factory:

const appReducer = compose(logging, combineReducers)(reducers);
//...
StoreModule.provideStore(appReducer),

Just watchout for setting up StoreModule and global app reducer, as that syntax has changed in recent ngrx versions since that blog post.

On a side note, if you're looking for some inspiration on implementing a meta-reducer to catch and invoke remote API calls, you might want to have a look at an equivalent, already-made middleware for Redux, as redux-api-middleware. HTH

Upvotes: 2

Related Questions