Developers continue to feel frustrated with the amount of boilerplate they have to write to take full advantage of the NgRx architecture in a strongly typed way. One of the largest sources of boilerplate is Actions. When writing Actions developers have to write the following code if they want to develop strongly typed reducers and effects:
Action Interface
interface LoginAction extends Action {
type: AuthActionType.LOGIN;
payload: {
username: string;
password: string;
}
}
Action Type Enum
enumAuthActionType {
LOGIN = '[Auth] Login',
}
Action Interface Union Type
typeAuthAction = LoginAction | LogoutAction;
Action Interface Lookup Type
typeAuthActionLookup = {
'[Auth] Login': LoginAction;
'[Auth] Logout': LogoutAction;
}
Note that we don’t actually ask developers to write this lookup type. However, by generating this type for them we can write a more strongly typed version of the Actions service in @ngrx/effects that can infer the shape of an action when providing an action type to the ofType() operator:
Note that we currently suggest developers write their actions as classes. This lets them write both the creator function and the interface in one motion, but it leads to confusion when we also suggest they use plain JavaScript objects.
Proposal
Write a codegen tool that can produce the majority of the Action boilerplate. Developers write action interfaces and the tool generates the action type enum, action union type, action lookup type, and action factory functions.
For example, given a file whose contents look like this:
Problem
interface LoginAction extends Action {
type: AuthActionType.LOGIN;
payload: {
username: string;
password: string;
}
}
enum AuthActionType {
LOGIN = '[Auth] Login',
}
type AuthAction = LoginAction | LogoutAction;
type AuthActionLookup = {
'[Auth] Login': LoginAction;
'[Auth] Logout': LogoutAction;
}
class Actions<T> extends Observable<T> {
ofType<U extends keyof T>(type: U): Observable<T[U]>;
}
function createLoginAction(payload: LoginAction["payload"]): LoginAction {
return {
type: '[Auth] Login',
payload,
};
}
Proposal
// auth.actions.ts
import { Action } from '@ngrx/store';