Friday, 25 November, 2016 UTC


Summary

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.
import { Injectable } from '@angular/core'; import { Action } from '@ngrx/store'; @Injectable() export class YoutubeVideosActions { static ADD = '[YoutubeVideos] ADD_VIDEOS'; static REMOVE = '[YoutubeVideos] REMOVE'; static RESET = '[YoutubeVideos] RESET'; static UPDATE_METADATA = '[YoutubeVideos] UPDATE_METADATA'; addVideos(videos: GoogleApiYouTubeVideoResource[]): Action { return { type: YoutubeVideosActions.ADD, payload: videos }; } removeVideo(): Action { return { type: YoutubeVideosActions.REMOVE }; } }
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.
import { Injectable } from '@angular/core'; import { Action } from '@ngrx/store'; import { ActionCreatorFactory } from '../action-creator.util'; @Injectable() export class YoutubeVideosActions { static ADD = '[YoutubeVideos] ADD_VIDEOS'; static REMOVE = '[YoutubeVideos] REMOVE'; static RESET = '[YoutubeVideos] RESET'; static UPDATE_METADATA = '[YoutubeVideos] UPDATE_METADATA'; addVideos = ActionCreatorFactory.create<GoogleApiYouTubeVideoResource[]>(YoutubeVideosActions.ADD); removeVideo = ActionCreatorFactory.create<void>(YoutubeVideosActions.REMOVE); reset = ActionCreatorFactory.create<void>(YoutubeVideosActions.RESET); updateMetaData = ActionCreatorFactory.create(YoutubeVideosActions.UPDATE_METADATA); }
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 ”” generic annotation, which allows to define a specific Type for the payload when “create” is used.
import { Action } from '@ngrx/store'; class ActionCreator<T> implements Action { constructor( public type: string = 'NOT_SET', public payload?: T ) {} } export class ActionCreatorFactory { static create?<T>(type: string, defaultPayloadValue?: any) { return (payload?: T) => { const _payload = payload || typeof payload !== 'undefined' ? payload : defaultPayloadValue; return new ActionCreator<T>(type, _payload); } }; }
Full code for the new YoutubeVideosActions is available on github.