Simple Action Creators for ngrx/store in Angular (2+)

In the development of Echoes Player (ng2 version),  I’m using ngrx/store for state management and ngrx/effects for logics with side effects. I’m always looking for better and simpler ways to write code – just experimenting with how code can be written differently. In this post I like to share a nice way for defining action creator functions which support typed arguments.

Before: Creating Action Creators

[UPDATE 26/12/2016]: you can now use ActionCreatorFactory via npm at https://github.com/orizens/ngrx-action-creator-factory

Up until now, I was using a simple and repetitive format for defining action creators. This is the “YoutubeVideosActions” which includes the available actions for managing the state of the videos store in Echoes Player.

Action creators encapsulates the creation of an “Action” object – it makes it safer and easier to create actions by calling the function “addVideos(newVideos)“, which takes a videos array as the payload of this action. Another action creator is the “removeVideo()” function, which in this case, takes no argument and just delivers an Action object with a “type” property only.

it’s a nice and clear way for defining action creators. However, when more actions are added, the file is getting bigger and the pattern for each action creator is repeated. This makes it hard to maintain and in my opinion, harder to have a glance at all action creators available in this file.

After: Action Creator Factory

I started to think of a different way for defining action creators – one which will allow me to have a better readable format, write less while keeping the useful typing of the payload argument. I came up with “ActionCreatorFactory.create()” – it creates a action creator function while the payload type is defined after the “create” function.

The first argument of the create function is the action that should be encapsulated. There an optional second argument which is a default value that the payload should be assigned to is no value has been triggered with the action creator.

The create function returns a function expression which uses ngrx/store “Action” interface.

I’m using two useful Typescript features:

First, I’m using the “public” declaration in the ActionCreator class to attach both arguments to the instance. A new instance of ActionCreator is a javascript object and this aligns with the contract of the “Action” interface.

Second, i’m using the “<T>generic annotation, which allows to define a specific Type for the payload when “create” is used.

Full code for the new YoutubeVideosActions is available on github.

  • Peter Nova

    This does seem to be more DRY than the approach taken by the ngrx example-app: https://github.com/ngrx/example-app Are there any downsides to taking this approach and might the example app be upgraded to this approach in the near future? I’ve generally tried to follow the example set forth by that repo

  • hi @peternova:disqus i’m also following the example app. Currently, this approach came from downsides that i’ve experienced with the old way, so I was looking for a more comfortable way that results in less writing and still typed.
    One thing that is missing is that actions are strings and there’s no check/assurance for actions override (that’s important for the reduce flow.
    I want to suggest this to the ngrx team and see how it goes.

  • Peter Nova

    ah yes, as far as checking for uniqueness, I see that they’re doing that here with this type util function that used to be called “label” but is now “type”: https://github.com/ngrx/example-app/blob/master/src/app/util.ts Thanks for all of your posts, they’ve been very enlightening along the way of understanding ngrx plus all the refactors that have happened with ngrx itself and the example app too

  • Tycho Grouwstra

    I’ve been thinking about whether this could be cleaned up further still, among the lines of putting the ‘data’ in an object e.g. { addVideos: YoutubeVideosActions.ADD } then going from there to construct in batch. However, I’ve been having trouble thinking of how the payload types could be ‘stored’ this way…
    P.S.: if you’re taking this idea back to ngrx/example-app, please link to your thread there! 🙂

  • hi @tychogrouwstra:disqus
    I thought of maybe using a custom decorator – but still experimenting with it.
    having payload type support is important for me, so i’m trying to get it right.

  • Tycho Grouwstra

    Interesting. I’d made decorators before, but I’m just not quite sure how to it’d work in this case — it feels like herding ghosts, since data and types essentially live in separate planes. I’d be curious to learn more, wish I had enough of a grasp of where you’re going to help out. 🙂

  • Tycho Grouwstra

    Having tried some more, I think I found a way now. So I wanted to get the types into the value domain so they could be contained in data structures. I achieved this by making a parameterized interface, then generating instances for it. You can then extract the generic from these instances (= values) to reuse in action generation.
    PoC:
    ts
    interface Tp {
    tp: T;
    }
    let c = (): Type<Tp> => class implements Tp {
    tp: T;
    }
    // example instance for string
    let tpCls = c();
    // at this point we can extract from the parameter to inject into a type parameter, for an existing makeAction function:
    let makeTypedAction = (cls: Type<Tp>) => makeAction()
    // usage:
    let typedAction = makeTypedAction(c());

    However, I fear even if one were to put this into data structures for class generation, the [lack of TypeScript support](https://github.com/Microsoft/TypeScript/issues/12393) for heterogeneous map() operations might still make you lose out on the information at the type level…

  • sounds interesting, though i’m always looking for the simplest solution to work with.

  • Tycho Grouwstra

    Yeah, curious what you’ll come up with here. I’d definitely like to make things simple, but I’ll admit the way to get there may not be simple.

  • Shlomi Assaf

    @orizens:disqus There a bug in the ActionCreatorFactory

    You assume that if the payload is falsy then return defaultPayloadValue.

    So sending “false” returns defaultPayloadValue, this is not the right behaviour especially for such a low level construct.

    It should be:
    payload || typeof payload !== ‘undefined’ ? payload : defaultPayloadValue

  • Thanks @shlomiassaf:disqus .
    I guess i missed this case.
    I updated the post.

  • Shlomi Assaf

    @orizens:disqus Great post BTW, I used this idea 🙂

    Check you FB spam message, sent you a message the other day probably didn’t go through.

  • yep – saw that now and replied back – thanks for the heads up.
    Thanks @Shlomi Assaf

  • Yosi Malki

    looking at the “updateIndexByMedia” “action now-playlist.actions.ts”
    why didnt you use an actionCreator but rather a regulra function that returns the action object ?

  • it’s a code written before ActionCreatorFactory was made – haven’t converted it yet.