Short explanation
I want to be able to write actions$.ofType<MyAction>().map(a => ...)
instead of actions$.filter(a => a.type === ActionType.MY_ACTION).map((a: MyAction) => ...)
.
Background
I'm trying to save some boilerplate when working with Redux in Angular by following the pattern laid out in this article: https://spin.atomicobject.com/2017/07/24/redux-action-pattern-typescript/. The TL;DR is that you can use string enums for the type field on your actions to simplify reducer code without sacrificing type safety. It involves making action interfaces that look like this:
export enum ActionType {
ADD = "Add",
NEGATE = "Negate"
}
export interface AddAction extends Action {
type: ActionType.ADD;
amount: number;
}
export interface NegateAction extends Action {
type: ActionType.NEGATE;
}
export type EveryAction = AddAction | NegateAction | DummyAction;
In reducers you can now write code like this:
switch (action.type) {
case TypeKeys.ADD: {
// The type of action is narrowed to AddAction here.
}
case TypeKeys.NEGATE: {
// ...and to NegateAction here.
}
}
Question
I'd like to apply a similar pattern when writing "epics" with redux-observable. I want a function ofType<T>()
that simultaneously filters by the type value on the action object and narrows to the correct derived action type. Note that redux-observable already has a non-generic ofType(), which takes the type value as an argument, but: 1) I'd rather pass the Action interface type as a generic argument instead of the type value, and 2) the type of the action after calling the built-in ofType() is still just Action.
This is what I want the code to look like:
export class Epics {
epics: Array<Epic<EveryAction, RootState>>;
constructor() {
this.epics = [
this.afterAdd,
this.afterNegate
];
}
public afterAdd = (actions$: Observable<EveryAction>) => actions$
.ofType<AddAction>()
.do(a => console.log(`Added ${a.amount} to value`)); // a is AddAction here
public afterNegate = (actions$: Observable<EveryAction>) => actions$
.ofType<NegateAction>()
.do(a => console.log("Negated value"));
}
However, I can't figure out how to write ofType<T>()
. I've been playing around with different TypeScript features like index types and keyof, but I can't get it to work. Is it possible?
I'd like the function to be ofType<T>()
, but if it has to be ofType(ActionType.INCREMENT)
then that's fine too, as long as it automatically narrows the action type without having to specify both the generic type and the the type key.