Skip to content

Commit

Permalink
feat(ofType): expose ofType as lettable operator (#343)
Browse files Browse the repository at this point in the history
Supports using ofType directly in lettable operator pipelines.

Closes #186
  • Loading branch information
jgoz authored and jayphelps committed Oct 26, 2017
1 parent 47f4f14 commit fb4a5af
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 19 deletions.
14 changes: 14 additions & 0 deletions docs/basics/Epics.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,20 @@ const pingEpic = action$ =>
.mapTo({ type: 'PONG' });
```

You can also use the `ofType()` operator directly as a [lettable operator](https://github.com/ReactiveX/rxjs/blob/master/doc/lettable-operators.md):

```js
import { ofType } from 'redux-observable';
import { delay, mapTo } from 'rxjs/operators'; // rxjs v5.5+

const pingEpic = action$ =>
action$.pipe(
ofType('PING'),
delay(1000), // Asynchronously wait 1000ms then continue
mapTo({ type: 'PONG' })
);
```

***

### Try It Live!
Expand Down
2 changes: 2 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,6 @@ export declare function createEpicMiddleware<T extends Action, S, D = any>(rootE
export declare function combineEpics<T extends Action, S, D = any>(...epics: Epic<T, S, D>[]): Epic<T, S, D>;
export declare function combineEpics<E>(...epics: E[]): E;

export declare function ofType<T extends Action>(...keys: T['type'][]): (source: Observable<T>) => Observable<T>;

export declare const EPIC_END: '@@redux-observable/EPIC_END';
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
"mocha": "^3.0.1",
"redux": "^3.5.2",
"rimraf": "^2.5.4",
"rxjs": "^5.0.0",
"rxjs": "^5.5.0",
"sinon": "^2.3.3",
"typescript": "^2.1.4",
"webpack": "^2.2.1",
Expand Down
17 changes: 3 additions & 14 deletions src/ActionsObservable.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { from } from 'rxjs/observable/from';
import { filter } from 'rxjs/operator/filter';
import { letProto } from 'rxjs/operator/let';
import { ofType } from './operators';

export class ActionsObservable extends Observable {
static of(...actions) {
Expand All @@ -24,18 +25,6 @@ export class ActionsObservable extends Observable {
}

ofType(...keys) {
return this::filter(({ type }) => {
const len = keys.length;
if (len === 1) {
return type === keys[0];
} else {
for (let i = 0; i < len; i++) {
if (keys[i] === type) {
return true;
}
}
}
return false;
});
return this::letProto(ofType(...keys));
}
}
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export { createEpicMiddleware } from './createEpicMiddleware';
export { ActionsObservable } from './ActionsObservable';
export { combineEpics } from './combineEpics';
export { EPIC_END } from './EPIC_END';
export { ofType } from './operators';
19 changes: 19 additions & 0 deletions src/operators.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { filter } from 'rxjs/operator/filter';

export function ofType(...keys) {
return function ofTypeOperatorFunction(source) {
return source::filter(({ type }) => {
const len = keys.length;
if (len === 1) {
return type === keys[0];
} else {
for (let i = 0; i < len; i++) {
if (keys[i] === type) {
return true;
}
}
}
return false;
});
};
}
56 changes: 56 additions & 0 deletions test/operators-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* globals describe it */
import { expect } from 'chai';
import { Subject } from 'rxjs/Subject';
import { ofType } from '../';

describe('operators', () => {
describe('ofType', () => {
it('should filter by action type', () => {
let actions = new Subject();
let lulz = [];
let haha = [];

actions.pipe(ofType('LULZ')).subscribe(x => lulz.push(x));
actions.pipe(ofType('HAHA')).subscribe(x => haha.push(x));

actions.next({ type: 'LULZ', i: 0 });

expect(lulz).to.deep.equal([{ type: 'LULZ', i: 0 }]);
expect(haha).to.deep.equal([]);

actions.next({ type: 'LULZ', i: 1 });

expect(lulz).to.deep.equal([{ type: 'LULZ', i: 0 }, { type: 'LULZ', i: 1 }]);
expect(haha).to.deep.equal([]);

actions.next({ type: 'HAHA', i: 0 });

expect(lulz).to.deep.equal([{ type: 'LULZ', i: 0 }, { type: 'LULZ', i: 1 }]);
expect(haha).to.deep.equal([{ type: 'HAHA', i: 0 }]);
});

it('should filter by multiple action types', () => {
let actions = new Subject();
let lulz = [];
let haha = [];

actions.pipe(ofType('LULZ', 'LARF')).subscribe(x => lulz.push(x));
actions.pipe(ofType('HAHA')).subscribe(x => haha.push(x));

actions.next({ type: 'LULZ', i: 0 });

expect(lulz).to.deep.equal([{ type: 'LULZ', i: 0 }]);
expect(haha).to.deep.equal([]);

actions.next({ type: 'LARF', i: 1 });

expect(lulz).to.deep.equal([{ type: 'LULZ', i: 0 }, { type: 'LARF', i: 1 }]);
expect(haha).to.deep.equal([]);

actions.next({ type: 'HAHA', i: 0 });

expect(lulz).to.deep.equal([{ type: 'LULZ', i: 0 }, { type: 'LARF', i: 1 }]);
expect(haha).to.deep.equal([{ type: 'HAHA', i: 0 }]);
});
});
});
22 changes: 18 additions & 4 deletions test/typings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import { expect } from 'chai';
import { createStore, applyMiddleware, MiddlewareAPI, Action } from 'redux';
import { Observable } from 'rxjs/Observable';
import { ajax } from 'rxjs/observable/dom/ajax';
import { map } from 'rxjs/operators/map';
import { asap } from 'rxjs/scheduler/asap';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/mapTo';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';

import { createEpicMiddleware, Epic, combineEpics,
EpicMiddleware, ActionsObservable } from '../';
EpicMiddleware, ActionsObservable, ofType } from '../';

interface State {
foo: string
Expand Down Expand Up @@ -70,8 +71,17 @@ const epic7: Epic<FluxStandardAction, State, Dependencies> = (action$, store, de
payload: dependencies.func(payload)
}));

const rootEpic1: Epic<FluxStandardAction, State> = combineEpics<FluxStandardAction, State>(epic1, epic2, epic3, epic4, epic5, epic6, epic7);
const rootEpic2 = combineEpics(epic1, epic2, epic3, epic4, epic5, epic6, epic7);
const epic8: Epic<FluxStandardAction, State, Dependencies> = (action$, store, dependencies) =>
action$.pipe(
ofType('EIGHTH'),
map(({ type, payload }) => ({
type: 'eighth',
payload
}))
)

const rootEpic1: Epic<FluxStandardAction, State> = combineEpics<FluxStandardAction, State>(epic1, epic2, epic3, epic4, epic5, epic6, epic7, epic8);
const rootEpic2 = combineEpics(epic1, epic2, epic3, epic4, epic5, epic6, epic7, epic8);

const dependencies: Dependencies = {
func(value: string) { return `func-${value}`}
Expand Down Expand Up @@ -118,6 +128,7 @@ store.dispatch({ type: 'SECOND' });
store.dispatch({ type: 'FIFTH', payload: 'fifth-payload' });
store.dispatch({ type: 'SIXTH', payload: 'sixth-payload' });
store.dispatch({ type: 'SEVENTH', payload: 'seventh-payload' });
store.dispatch({ type: 'EIGHTH', payload: 'eighth-payload' });

expect(store.getState()).to.deep.equal([
{ "type": "@@redux/INIT" },
Expand Down Expand Up @@ -157,7 +168,10 @@ expect(store.getState()).to.deep.equal([
{ "type": "sixth", "payload": "sixth-payload" },
{ "type": "SEVENTH", "payload": "seventh-payload" },
{ "type": "seventh", "payload": "func-seventh-payload" },
{ "type": "seventh", "payload": "func-seventh-payload" }
{ "type": "seventh", "payload": "func-seventh-payload" },
{ "type": "EIGHTH", "payload": "eighth-payload" },
{ "type": "eighth", "payload": "eighth-payload" },
{ "type": "eighth", "payload": "eighth-payload" }
]);

const input$ = Observable.create(() => {});
Expand Down

0 comments on commit fb4a5af

Please sign in to comment.