Jim Lynch Codes
  • Blog
  • About Jim

Writings about one coder's stories & experiences.

Using Classes To Define Your Ngrx Actions

12/21/2016

0 Comments

 
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:  
export const INCREMENT:string = "INCREMENT";
export const EVENT_FROM_EFFECT:string = "EVENT_FROM_EFFECT";
And then maybe dispatching an action in your smart component would look like this:
import { INCREMENT } from "../../actions";

this.store.dispatch({ type: INCREMENT, payload:  {text: "derp!"} });
And your reducer might look something like this:
import {ActionReducer, Action} from "@ngrx/store";
import {State, intitialState} from "../state/main-state";
import {INCREMENT, EVENT_FROM_EFFECT} from "../../actions";

export const mainReducer: ActionReducer<State> =
  (state = intitialState, action: Action) => {

    console.log('Action came in! ' + action.type);

    switch (action.type) {

      case INCREMENT: {
        console.log('the payload string is: ' + action.payload.innerObj.text);
        return {
          counter: state.counter + 1
        }
      }

      case EVENT_FROM_EFFECT: {
        console.log('we cheesin in the reducer over here!');
        return {
          counter: 4
        }
      }
    }
  }

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.
import {Action} from "@ngrx/store";

export const ActionTypes = {
  SEARCH:           'SEARCH',
  GET_VIDEOS:       'GET_VIDEOS'
};

interface IVideosObj {
   something:string;
   others:string;

}

export class SearchAction implements Action {
  type = ActionTypes.SEARCH;

  constructor(public payload: string) { }
}

export class GetVideosAction implements Action {
  type = ActionTypes.GET_VIDEOS;

  constructor(public payload: IVideosObj) { }
}

export type Actions =
  SearchAction |
  GetVideosAction;
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. 
import {Store} from '@ngrx/store';
import {State} from "../../main-reducer";

constructor(private store:Store<State>) {

    this.store.dispatch(new actionCreator.SearchAction());

}
Notice that the type system won't even allow you to create an action with the wrong number of parameters:
Picture
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":
Picture
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. 
import {ActionReducer, Action} from '@ngrx/store';
import {ActionTypes} from "../actions/main-action-creator";

export const mainStoreReducer: ActionReducer<State> =
  (state = intitialState, action: Action) => {

    switch (action.type) {
      case ActionTypes.AUTH_SUCCESS: {
        .....
      }

      case ActionTypes.BEGIN_ANONYMOUS_AUTH: {
        ......
      }

    }
  }
}

Stay Classy, San Diego

I hope you've found this post useful and will consider using TypeScript classes for your own ngrx actions. TypeScript gives you options; you can harness the power it gives you or you could ignore it and go about your coding as if everything was still Es5 JavaScript. In my opinion, the bit of extra work to set up and understand code with nice type-checking is usually worth it in the long run.  What do you think? Are you on board with the classy actions? Have you found a better way to do things? Either way, I'd love to hear from you! :)
0 Comments

Your comment will be posted after it is approved.


Leave a Reply.

    ​Author

    Picture
    The posts on this site are written and maintained by Jim Lynch. About Jim...
    Follow @JimLynchCodes
    Follow @JimLynchCodes

    Want FREE access to
    my daily stock tips 
    ​newsletters??
    Sign up here:

    - Triple Gainers
    - Rippers N' Dippers
    - Growingest Growers
    ​

    Categories

    All
    Actionscript 3
    Angular
    AngularJS
    Automated Testing
    AWS Lambda
    Behavior Driven Development
    Blogging
    Business Building
    C#
    C / C++
    ClojureScript / Clojure
    Coding
    Community Service
    CS Philosophy
    Css / Scss
    Dev Ops
    Firebase
    Fitness
    Flash
    Front End
    Functional Programming
    Git
    Go Lang
    Haskell
    Illustrations
    Java
    Javascript
    Lean
    Life
    Linux
    Logic Pro
    Music
    Node.js
    Planning
    Productivity
    Professionalism
    Python
    React
    Redux / Ngrx
    Refactoring
    Reusable Components
    Rust
    Security
    Serverless
    Shell Scripting
    Swift
    Test Driven Development
    Things
    TypeScript
    Useful Sites
    Useful Tools
    Video
    Website Development
    WebStorm
    Writing

    Archives

    February 2021
    January 2021
    October 2020
    September 2020
    May 2020
    April 2020
    February 2020
    January 2020
    December 2019
    October 2019
    September 2019
    August 2019
    July 2019
    June 2019
    May 2019
    April 2019
    March 2019
    February 2019
    January 2019
    December 2018
    November 2018
    October 2018
    September 2018
    August 2018
    June 2018
    May 2018
    April 2018
    March 2018
    February 2018
    January 2018
    December 2017
    November 2017
    October 2017
    September 2017
    August 2017
    July 2017
    May 2017
    April 2017
    March 2017
    February 2017
    January 2017
    December 2016
    November 2016
    October 2016
    September 2016
    August 2016
    July 2016
    June 2016
    May 2016
    April 2016
    March 2016
    February 2016
    January 2016
    December 2015
    November 2015
    October 2015

    RSS Feed

  • Blog
  • About Jim
JimLynchCodes © 2021