I don’t know about you, but one of the main problems I’ve always had with NgRX is that the Action Type relies on strings. And, every time I create a new set of Actions, I typically copy and paste from an existing actions file and modify the strings. Of course, some times I forget and then I end up with code in two different places firing at strange times and getting results showing up that I didn’t expect.
Getting On The Same Page
First, let’s review what an NgRX Actions file should look like:
You may have seen a form of this file that uses constants for the strings instead of the enum type.
In some ways, using const is easier. But, by using enum we get the added advantage of not having to cast our actions in our Reducer because the TypeScript Discriminated Union feature kicks in.
Discriminated Unions
A Discriminated Union allows us to use a switch or an IF statement against an enum and because we’ve done so, it automatically knows that they type we are dealing with matches that type. See the section on Discriminated Unions on this page https://www.typescriptlang.org/docs/handbook/advanced-types.html.
What this means is that our reducers can be coded like this:
You should notice that we specified that the editReducer
function specifies Edit.Actions a the parameter type that is passed in and yet on line 16, the code knows that action.form exist because it is of type UPDATE.
If you aren’t familiar with this form, you’ll notice that we didn’t provide a type for the type property in our classes. If we do, we no longer get the benefit of our reducer being able to deduce which type of Action our action is. This one place where string typing might get in the way.
Unique Actions
And now that we are all on the same page regarding how to create and use Actions in NgRX, the next step is to ensure our Actions are unique.
For this we will need to create a new function called isUniqueString(val: string);
The purpose of this function is to ensure that each string it is passed is unique from every previous string it was passed.
This function takes advantage of JavaScript’s, and therefore TypeScript’s ability to reference object fields using the string indexing operator. So, we simply check to see, does this field already exist in the object? If it does, throw an exception. If not, register it.
For those of you who might be concerned that uniqueStringMap is a global variable that isn’t encapsulated. Because of the way that the modern bundelers work, uniqueStringMap only exist and is only available to the file it is declared in because we never exported it.
To use this, we add a small bit of code at the end of each of our actions files.
Here we are making use of Object.values()
to turn our Type enum
into an array of the enum’s values. Then we are using forEach()
to loop through those keys and check them with isUniqueString().
Now, what may not be obvious about this code is that it will run as soon as it is loaded. If there is a collision with another action, an exceptions will get thrown and your code will stop working. This makes it painfully obvious that you have a problem and because we tell you exactly what key is in conflict you know exactly where to look.
Problem solved.