Dispatch Strongly Typed Action Objects to an ngrx Store in Angular

Lukas Ruebbelke
InstructorLukas Ruebbelke

Share this video with your friends

Send Tweet
Published 4 years ago
Updated 4 years ago

Actions in an ngrx application are fundamentally very simple with a type property and usually a payload property. As a result, actions can be expressed as object literals with their types being nothing more than a simple string. This presents some serious logistical challenges as your application starts to grow in complexity by increasing the chance of action type collisions and opaque information when it is time to debug. This is where strongly typed action objects come into play as we replace our generic action objects with typesafe actions that pull their types from a strongly typed enum with clear information about what that action does. We will see how this makes our application a lot more readable and gives us better information in our developer tools.

Instructor: [00:00] In this lesson, we are going to learn how to use strongly typed actions to our advantage.

[00:06] One, by using typesafe actions, that we can move away from these generic object literals and create some type safety by not only controlling the type of action that goes through our system, but also being far more descriptive in terms of what is actually flowing through our application.

[00:29] If we look at our reducer right now, we have select, create, update, and delete, but there's really no other information. What are we selecting, creating, updating, or deleting? Is it a project? Is it a customer? We do not know.

[00:44] What I recommend is using typesafe or strongly typed action objects that are very descriptive about the action that you are performing in your application. Stepping into our actions file, let's go ahead and start to build this out.

[01:06] Instead of using opaque actions with these generic titles, what we're going to do is create an enum that describes the type of actions that we want to make available in our application. We'll call this ProjectsActionTypes. Within here, we are going to define the possible actions that we can take.

[01:32] We'll go ProjectSelected. The value that you assign to this can and should be very explicit. The convention that I like is I put the feature that I'm working on in brackets, such as Projects. Then, I'll state what I am doing. In this case, it's selecting. For AddProject, I would say "Projects Add Data." You can imagine update data, delete data.

[02:00] Now, we're being very explicit about the type of action that we are performing, which gives far more actionable data inside of our dev tools as we are troubleshooting our application. Seeing something such as ProjectSelected or Projects Add Data, we know exactly what the action was that fired that.

[02:24] Now that we've defined our action types enum, let's go ahead and define our action types. This is going to be a strongly typed action. What we're going to do is we're going to create an action that is going to implement the action interface.

[02:48] We'll start with SelectProject. From here, we are going to give it a type property. If you look at the interface itself, it has one property on that. That is type. We'll set this to the enum of ProjectSelected.

[03:10] Then, we will define our constructor. This is where we will define our payload as a parameter into our constructor. When you create or a concrete instance of this action, you pass that in. Not every action has a payload, but most of them do.

[03:31] Let's go ahead and let's build out the rest of our action objects. I'm just going to copy this. I will paste it in. Just for the sake of time, I'm not going to manually type these all out, but rather just update these.

[03:48] We'll do AddProject. We will update the type. We'll do UpdateProject. Update the type, as well as DeleteProject. Again, NgRx is very, very conventional. Once you understand the pattern in one place, you'll see that it carries over through the rest of the application.

[04:10] Now that we have defined the actions that we want to use, we will make them available by exporting a union type of project actions. We will do SelectProject.

[04:30] Using this pipe, we are going to then create a union type of AddProject, UpdateProject, or DeleteProject. This is just a convenient way to export all of these projects or make them available as a single property or a property type within our application.

[04:58] Now that we have exposed our ProjectsActions as a union type, now, we need to update the rest of our application to move away from these opaque object literals to use the strongly typed action. Let's go ahead and let's import our action types into our projects reducer. Import ProjectsActionTypes.

[05:29] Let's hop into our reducer. Instead of now listening for a string or evaluating to a string, we are going to update the switch statement to use the enum. This is much, much more descriptive. You're no longer using these magic strings to evaluate on or within our switch case.

[05:54] We'll go ahead and work through and update the cases to use the enums. Now that we're done with that, let's go into our barrel roll here. We're just going to make these actions available to any outside application that wants to consume them.

[06:24] What we're going to do is just export our ProjectsActions. Within that, then we will then add in the specific action types that we want to make available, or the specific actions rather.

[06:45] Now that we've exported these, we can go into our ProjectsComponent. We can replace these loosely typed generic objects with strongly typed actions. Let's go here. We'll just replace this. We are going to go new AddProject. We're going to send in the project as the payload.

[07:13] Let's do the same for UpdateProject, new UpdateProject. We are creating a concrete action instance, sending in the project as the payload. Let's do the same for the DeleteProject. We're going to new up the DeleteProject action, sending in project as the payload.

[07:43] Now that we've done that, let's go ahead, hop into the app. Let's see this working. There we go. Project deleted. If we step into the dev tools, now, you can see that the information coming through is much more descriptive, which is exactly what you want to happen.

[08:09] Let's do a quick recap. Let's go into the project actions. You can see that the first thing we did is created a ProjectsActions enum, defining our types. From here, we went into our reducer. We updated the reducer to evaluate on those types. We exported our individual actions that we created.

[08:33] Within our ProjectsComponent, we went through and removed those generic object literals with concrete instances of the action objects that we created. This is how you use typesafe actions within your Angular application with NgRx.

Varghese Pallathu
Varghese Pallathu
~ 3 years ago

My question is around checking the cached data and dispatching the action only if there is no cached data.

I have a 'products' observable which is streamed from the products that were retrieved from the server and stored in the store. The code looks like before. I saw a few different solutions like check the store state before dispatching or check the state inside the effect etc... What do you suggest?

<b>ngOnInit() {
    this.products = this.store.pipe(select(ProductSelectors.selectAllProducts));
     // Dispatch only if this.products$ does not emit any data
     this.store.dispatch(ProductActions.LoadProducts());
  }</b>
Lukas Ruebbelke
Lukas Ruebbelkeinstructor
~ 3 years ago

Hi Varghese - it would be fairly easy to check if your selector returns anything and if not, dispatch an action to pull remote data. If you wanted to implement a more sophisticated caching strategy, I would recommend implementing that logic inside of an effect.

Varghese Pallathu
Varghese Pallathu
~ 3 years ago

Thank you. I finally implemented in the effect itself. I answered my own question in stackoverflow

Varghese Pallathu
Varghese Pallathu
~ 3 years ago

I have a contact application where I list the contact information as names as a master list. Then the individual item can be clicked and the details will be shown. I'm planning to download some 500 to 100 contacts and their details like address, email address, biography to the store which is a reasonably large document from the mongodb.

What does your experience say when it comes to download large amounts of data to the browser? Is there any size that I need to worry about when it comes to how much data I can keep in a ngrx store which correlates to the browser memory?

Lukas Ruebbelke
Lukas Ruebbelkeinstructor
~ 3 years ago

This depends on a variety of factors but typically when I find performance issues because of too much data in the browser are generally an indication of a larger UX issue. How many records can a user comfortably parse at any given time? Maybe 20 or 30... but definitely not 500. With large datasets, I generally implement a paging mechanism and scope the data set to what is comfortable to the user. You could also store a subset of data such as id, firstName, lastName and then lazy load the rest of the record when a user interacts with that contact. The point being that when you optimize for the user experience, performance issues tend to become a secondary concern.

Varghese Pallathu
Varghese Pallathu
~ 3 years ago

I agree to what you said. I should download 10 or 20 contacts at a time. But I have a few other reasons which I did not mention in the previous message.

One reason I wanted to download all of them at once was to avoid making server calls. I get the data from the Microsoft CosmosDB, so there is charge for every API call. The other reason is that I want to cache the data in the browser IndexedDB so the app can work without internet connection. From an implementation perspective, download all of them at once is the easiest implementation, and then retrieve the data from the local or the ngrx store and not from the backend database.