I've been working a lot recently with the Angular state management library ngr. I even wrote a blog post about setting up your Angular 2 project with ngrx/store and then a follow up post The Basics of"ngrx/effects", @Effect, and Async Middleware for "ngrx/store" in Angular 2. However, after taking a closer look at the official ngrx example project I learned a little trick that harnesses the power of TypeScript to make your coding experience even more fun and less error-prone.
Reviewing Ngrx Data Flow
Let's go over how data should flow in an Angular 2 app that uses ngrx. Suppose the user clicks a button. That button triggers a handler function in the corresponding dumb component. Then that dumb component emits an event up to the parent smart component. The smart component then dispatches an action to the store. The action then is handled by a reducer (or an @Effect and then a reducer for async calls for data). The reducer then updates the state, and since the smart component was subscribing to state changes this whole time then the data that the smart component gets from the store is immediately updated when the reducer sets a new state. This values in the smart component are linked to the dumb component's @Input() properties, and so the dumb component is also immediately updated when the reducer updates the state.
The Noob Way
The noob way is not all that bad. It does work, and it's a heck of a lot better than not using ngrx at all. It can also be a great way to understand ngrx for those just getting started. However, for out production-level code we can do better! Let's understand what the noob way all about. Really, it comes down to thinking of your actions as strings. For example, you might define your actions like this:
And then maybe dispatching an action in your smart component would look like this:
And your reducer might look something like this:
A More Classy Way
Now that we're working with Angular 2, we've got the power of TypeScript behind us so we might as well use it! Notice how in the example above when we are writing out the action to be dispatched from the smart component we get no type-checking whatsoever. Sigh. But in TypeScript we can create our own types, and we can do that by just creating classes! And that's the key here: thinking of your actions as classes. I'll be honest, it looks a little verbose at first, but I think you'll like it in the end.
In the code snippet above it may seem like there's a lot going, but it's really not that bad. We're exporting a bunch of different things so let's go through them. First, we're creating an object ActionTypes that will contain all the various strings that represent our action types (duh hehe). Next, we define an interface that we'll use in our action class. Then we define the classes that we will use for our actions. Every one of our actions will implement Action from the ngrx/store library. Implementing this action is basically an agreement that this class will have a type property and a payload property. Notice how we use the slick TypeScript syntax to define the payload in the constructor. Since this property is coming through in the constructor we know that it can be passed in to this action when the class is instantiated. Also noticed the public access modifier. This alows other classes to acces this property, and since we're defining the public var in the constructor we know that this property will also be created as a field variable in the class. Then finally we export a union type, meaning that it can be any of the types that we difine by our action classes.
So now that we've created the actions let's see how to use them to dispatch actions in our components.
Notice that the type system won't even allow you to create an action with the wrong number of parameters:
And we can get even better than that; the type system knows exactly what type the payload should be, and if you try to pass in some arguments of the wrong type your IDE will immediately let you know at "author time":
Even if your payload is an object with nested objects, the IDE will prevent you from passing in anything that's not the right type! (thanks buddy) :)
The New Reducer
Our reducer function really doesn't change that much. In this simplified example below we can see that it's pretty much the same as the standard ngrx reducer function. However, instead of putting the raw strings for the action types in the case statements I'm importing my ActionTypes object for some extra type checking that will quickly catch any misspelled action types.
Stay Classy, San Diego
The posts on this site are written and maintained by Jim Lynch. About Jim...