Skip to content
Permalink
Browse files

feat(replaceEpic): Added middleware method to replace the root Epic. …

…Useful for code splitting and hot reloading
  • Loading branch information...
jayphelps committed Jul 16, 2016
1 parent a2b59ac commit a8f458d1bf8db92c59a3e590869410e9ad73bf83
@@ -38,6 +38,7 @@ To see redux-observable in action, here's a very simple JSBin to play around wit
* [API Reference](docs/api/SUMMARY.md)
* [createEpicMiddleware](docs/api/createEpicMiddleware.md)
* [combineEpics](docs/api/combineEpics.md)
* [EpicMiddleware](docs/api/EpicMiddleware.md)
* [CHANGELOG](CHANGELOG.md)

:shipit:
@@ -10,4 +10,5 @@
* [API Reference](docs/api/SUMMARY.md)
* [createEpicMiddleware](docs/api/createEpicMiddleware.md)
* [combineEpics](docs/api/combineEpics.md)
* [EpicMiddleware](docs/api/EpicMiddleware.md)
* [CHANGELOG](/CHANGELOG.md)
@@ -10,4 +10,5 @@
* [API Reference](api/SUMMARY.md)
* [createEpicMiddleware](api/createEpicMiddleware.md)
* [combineEpics](api/combineEpics.md)
* [EpicMiddleware](api/EpicMiddleware.md)
* [CHANGELOG](/CHANGELOG.md)
@@ -0,0 +1,21 @@
# EpicMiddleware

An instance of the redux-observable middleware.

To create it, pass your root [Epic](../basics/Epics.md) to [`createEpicMiddleware`](createEpicMiddleware.md).

### EpicMiddleware Methods

- [`replaceEpic(nextEpic)`](#replaceEpic)

<hr>

### <a id='replaceEpic'></a>[`replaceEpic(nextEpic)`](#replaceEpic)

Replaces the epic currently used by the middleware.

It is an advanced API. You might need this if your app implements code splitting, and you want to load some of the epics dynamically or you implement use hot reloading.

#### Arguments

1. `epic` (*Epic*) The next epic for the middleware to use.
@@ -2,3 +2,4 @@

* [createEpicMiddleware](createEpicMiddleware.md)
* [combineEpics](combineEpics.md)
* [EpicMiddleware](EpicMiddleware.md)
@@ -6,7 +6,7 @@ Now that we know what [Epics](Epics.md) are, we need to provide them to the redu

Just like redux requiring a single root Reducer, redux-observable also requires you to have a single root Epic. As we [learned previously](Epics.md), we can use `combineEpics()` to accomplish this.

One common pattern is to import all your Epics into a single file, which then combined root Epic along with your root Reducer.
One common pattern is to import all your Epics into a single file, which then exports the root Epic, along with your root Reducer.

### redux/modules/root.js

@@ -12,6 +12,10 @@ export declare interface Epic {
(action$: ActionsObservable, store: MiddlewareAPI<any>): Observable<Action>;
}

export declare function createEpicMiddleware(rootEpic: Epic): Middleware;
export interface EpicMiddleware extends Middleware {
replaceEpic(nextEpic: Epic): void;
}

export declare function createEpicMiddleware(rootEpic: Epic): EpicMiddleware;

export declare function combineEpics(...epics: Epic[]): Epic;
@@ -1,31 +1,37 @@
import { Subject } from 'rxjs/Subject';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { from } from 'rxjs/observable/from';
import { ActionsObservable } from './ActionsObservable';

export function createEpicMiddleware(epic) {
let actions = new Subject();
let actionsObs = new ActionsObservable(actions);
const actionsSubject = new Subject();
const action$ = new ActionsObservable(actionsSubject);
const epic$ = new BehaviorSubject(epic);

let middleware = (store) => (next) => {
if (epic) {
epic(actionsObs, store).subscribe(store.dispatch);
}
return (action) => {
const epicMiddleware = store => next => {
epic$.filter(epic => typeof epic === 'function')
.switchMap(epic => epic(action$, store))
.subscribe(store.dispatch);

return action => {
if (typeof action === 'function') {
if (typeof console !== 'undefined' && typeof console.warn !== 'undefined') {
console.warn('DEPRECATION: Using thunkservables with redux-observable is now deprecated in favor of the new "Epics" feature. See http://redux-observable.js.org/docs/FAQ.html#why-were-thunkservables-deprecated');
}

let obs = from(action(actionsObs, store));
let sub = obs.subscribe(store.dispatch);
return sub;
const out$ = from(action(action$, store));
return out$.subscribe(store.dispatch);
} else {
let result = next(action);
actions.next(action);
const result = next(action);
actionsSubject.next(action);
return result;
}
};
};

return middleware;
epicMiddleware.replaceEpic = epic => {
epic$.next(epic);
};

return epicMiddleware;
}
@@ -13,10 +13,10 @@ const { Observable } = Rx;
describe('createEpicMiddleware', () => {
it('should accept a epic argument that wires up a stream of actions to a stream of actions', () => {
const reducer = (state = [], action) => state.concat(action);
const epic = (actions, store) =>
const epic = (action$, store) =>
Observable.merge(
actions.ofType('FIRE_1').mapTo({ type: 'ACTION_1' }),
actions.ofType('FIRE_2').mapTo({ type: 'ACTION_2' })
action$.ofType('FIRE_1').mapTo({ type: 'ACTION_1' }),
action$.ofType('FIRE_2').mapTo({ type: 'ACTION_2' })
);

const middleware = createEpicMiddleware(epic);
@@ -35,6 +35,56 @@ describe('createEpicMiddleware', () => {
]);
});

it('should allow you to replace the root epic with middleware.replaceEpic(epic)', () => {
const reducer = (state = [], action) => state.concat(action);
const epic1 = action$ =>
Observable.merge(
Observable.of({ type: 'EPIC_1' }),
action$.ofType('FIRE_1').mapTo({ type: 'ACTION_1' }),
action$.ofType('FIRE_2').mapTo({ type: 'ACTION_2' }),
action$.ofType('FIRE_GENERIC').mapTo({ type: 'EPIC_1_GENERIC' })
);
const epic2 = action$ =>
Observable.merge(
Observable.of({ type: 'EPIC_2' }),
action$.ofType('FIRE_3').mapTo({ type: 'ACTION_3' }),
action$.ofType('FIRE_4').mapTo({ type: 'ACTION_4' }),
action$.ofType('FIRE_GENERIC').mapTo({ type: 'EPIC_2_GENERIC' })
);

const middleware = createEpicMiddleware(epic1);

const store = createStore(reducer, applyMiddleware(middleware));

store.dispatch({ type: 'FIRE_1' });
store.dispatch({ type: 'FIRE_2' });
store.dispatch({ type: 'FIRE_GENERIC' });

middleware.replaceEpic(epic2);

store.dispatch({ type: 'FIRE_3' });
store.dispatch({ type: 'FIRE_4' });
store.dispatch({ type: 'FIRE_GENERIC' });

expect(store.getState()).to.deep.equal([
{ type: '@@redux/INIT' },
{ type: 'EPIC_1' },
{ type: 'FIRE_1' },
{ type: 'ACTION_1' },
{ type: 'FIRE_2' },
{ type: 'ACTION_2' },
{ type: 'FIRE_GENERIC' },
{ type: 'EPIC_1_GENERIC' },
{ type: 'EPIC_2' },
{ type: 'FIRE_3' },
{ type: 'ACTION_3' },
{ type: 'FIRE_4' },
{ type: 'ACTION_4' },
{ type: 'FIRE_GENERIC' },
{ type: 'EPIC_2_GENERIC' },
]);
});

it('emit warning that thunkservable are deprecated', () => {
sinon.spy(console, 'warn');

@@ -165,11 +215,11 @@ describe('createEpicMiddleware', () => {
const store = createStore(reducer, applyMiddleware(middleware));

store.dispatch(
(actions) => Observable.of({ type: 'ASYNC_ACTION_2' })
(action$) => Observable.of({ type: 'ASYNC_ACTION_2' })
.delay(10)
.takeUntil(actions.filter(action => action.type === 'ASYNC_ACTION_ABORT'))
.takeUntil(action$.filter(action => action.type === 'ASYNC_ACTION_ABORT'))
.merge(
actions
action$
.map(action => ({ type: action.type + '_MERGED' }))
.take(1)
)
@@ -197,8 +247,8 @@ describe('createEpicMiddleware', () => {

const store = createStore(reducer, applyMiddleware(middleware));

const action2 = (actions) => Observable.of({ type: 'ASYNC_ACTION_2' });
const action1 = (actions) => Observable.of({ type: 'ASYNC_ACTION_1' }, action2);
const action2 = (action$) => Observable.of({ type: 'ASYNC_ACTION_2' });
const action1 = (action$) => Observable.of({ type: 'ASYNC_ACTION_1' }, action2);

store.dispatch(action1);

@@ -1,24 +1,28 @@
import { createEpicMiddleware, combineEpics, ActionsObservable } from '../index';
import { createEpicMiddleware, Epic, combineEpics,
EpicMiddleware, ActionsObservable } from '../index';
import { Action } from 'redux';
import { Observable } from 'rxjs/Observable';

const epic1 = (action$, store) =>
const epic1: Epic = (action$, store) =>
action$.ofType('FIRST')
.mapTo({
type: 'first',
payload: store.getState()
});

const epic2 = (action$, store) =>
const epic2: Epic = (action$, store) =>
action$.ofType('SECOND')
.mapTo({
type: 'second',
payload: store.getState()
});

const rootEpic = combineEpics(epic1, epic2);
const rootEpic1: Epic = combineEpics(epic1, epic2);
const rootEpic2: Epic = combineEpics(epic1, epic2);

createEpicMiddleware(rootEpic);
const epicMiddleware: EpicMiddleware = createEpicMiddleware(rootEpic1);

epicMiddleware.replaceEpic(rootEpic2);

// should be a constructor that returns an observable
const actionsSubject = Observable.create(() => {});

0 comments on commit a8f458d

Please sign in to comment.
You can’t perform that action at this time.