Data manipulation on collections can be expensive because it is usually necessary to iterate over the collection at least once to perform any kind of update or selection to it. A common pattern for avoiding expensive iterations is to break a collection into a key-value store that allows for instantaneous lookup of value provided you have the key. We could handle this data transformation ourselves but ngrx entity was created to do all the heavy lifting for us. In this lesson, we will see how to update how we define our feature state to use EntityState
, create an EntityAdapter
, define our initial state using our adapter and most importantly, how to leverage the convenience methods that the adapter gives us in our reducer. We will also examine how ngrx entity changes the shape of our state by splitting our collection into ids
and entities
and how to update our feature selector to work with it.
Instructor: [00:00] In this lesson, we are going to learn how to simplify our collection manipulation with NgRx entity. Working with collections as a whole can be a little bit difficult when you need to either find a selected item or delete or update an item.
[00:19] Usually what that means is you have to iterate over the collection until you find the thing that you need and then perform your operation. If you're doing this over and over and over, that can be very expensive.
[00:31] A common pattern is to break your collection into a key-value store. Therefore, if you have the key or the ID for your object, then you can look it up in almost real time and find that object, manipulate it or delete it or whatever you need to do. Then, move forward. Having to iterate over a collection over and over is very, very time-consuming.
[00:57] We are going to integrate entity into our application. The first thing we need to do is we're going to add the ability to load project. We're going to have to do a little bit of groundwork here to get this working. It'll be worth it in the end.
[01:13] The first thing we'll do is add in a load projects projects action type for projects load data. Then we're going to just create an action object for this particular action type. We'll call this load projects. This will implement the action interface. We'll set the type.
[01:38] We'll also set the constructor initially. When we say load projects, we're going to pass in the projects that we want to load, which we will pull from the initial projects once we move over to the reducer.
[01:58] Now that we have our load projects action defined, let's go ahead and add this to the union type. Then from here, let's hop into our projects reducer. We're going to change a few things here. The first thing that we're going to do is we're going to move away from this custom interface that we created at the beginning.
[02:26] Let's go ahead. Let's take this project state interface. Let's rewrite this. We're still defining an interface. It's going to project state. It is going to extend the entity state that ships with NgRx entity. It's a generic. It will take a project object.
[02:51] Then we're going to define our custom property on here, which is selected project ID. Under the hood, it's going to assume that it's dealing with a project in a collection of projects.
[03:06] You can look here in the entity state interface that it has two main properties, IDs and entities. When you send in your collection, under the hood, it will break it up into that key-value store for you where it will have a key-value store as well as it keeps track of the IDs in the IDs array.
[03:25] The next step that we need to do, instead of defining our initial state, is we need to initialize our entity adapter. We're going to create an adapter. It's going to be of type entity adapter. Because it's generic, we'll give it a project property. Then we'll go create entity adapter. Again, type the generic portion of this. There we go.
[03:59] Now we are ready to define our initial state. Because that the IDs and the entities are handled under the hood, what we're going to do is instead pull initial state off of the adapter that we just created and then define the additional custom properties that we want to put on our initial state. In this case, it's going to be just selected project ID. We'll go ahead and set that to null.
[04:35] Now that we have our adapter defined, we'll go ahead and export the initial projects so that it is available outside of the application so that when we call load projects, we can pass that in. That's just a temporary step.
[04:48] Now we'll go down to the reducer. We can get rid of some of these manual methods that we created, create project, update project, etc. We can start to replace these with some convenience functions that were provided to us from the entity that we created.
[05:09] Project selected is going to be the odd one here. Instead, we're just going to return a brand new object. Using object at assign, we're just going to update the selected project ID. This is the one odd one because we're not dealing with the collection itself but the custom property that we created.
[05:31] Now what we'll do is, because load projects, add projects, update projects, and delete project are going to be pretty much the same, let's go ahead and let's gut these switch cases here.
[05:45] We are going to replace them with a new simplified version that's available to us because of the entity adapter that we created. We're just going to return adapter.addmany. We'll pass in the action payload and state.
[06:03] In this case, we're saying, "Hey, we wanna add many of, many projects if you will to the collection. And then from here, we will add in add one, upsert one, and then we will do remove one." Now that we have that available, let's go ahead, hop into our barrel load.
[06:25] We're just going to make initial projects available to our application. What we're going to do now is just manually pass that in when we fire off a load projects action. We go into projects component. Let's go ahead and get projects. Let's dispatch a load projects action, new load projects. We will pass in initial projects. We should be good to go.
[07:00] Let me just make sure that this imported correctly. It did.
[07:04] Now let's delete this selection within the observable stream because the underlying structure is going to change. Let's go ahead and trace this out. We can see what we're working with, projects async pipe JSON. Remember, we're dealing with IDs and entities. Let's trace this out.
[07:27] You can see here that this is an entirely new collection. Within the observable stream, we need to change how we're servicing the data that we need. First things first, let's go ahead and map data entities. You can see now that we have a key-value pair that we can work with.
[07:50] Let's go ahead and do one more map operation. We're just going to pull the keys off, loop over it, and then assign it to the underlying object. We'll go objects.keys map. Then we'll return data. This is basically a line for this instantaneous lookup.
[08:10] Now if we go into our browser, let's take a look. Now we're dealing with an array. Map returns an array. Then we're populating with the value based on the key. We can uncomment this, delete this statement here. We should be good to go.
[08:29] One thing that we do need to do as well is that, because we're using the entity to delete it, we need to send in the ID. We can go ahead and get rid of this.getprojects because we no longer need to rehydrate this every single time we do an operation. Now we can go and see that if we select this, that we can delete it.
[08:55] Now any kind of operation that we do, create, read, update, and delete, is now being handled by NgRx entity and via these convenience functions that we surfaced in our reducer. We added the load projects action type. Then from there, we redefined our basically feature state via the entity state interface.
[09:23] We initialized the adapter using create entity adapter. Then we set our initial state using adapter.getinitialstate and sending in our custom property. Then, the best part of all is that we were able to, in our reducer, defer this workload to the adapter itself and not having to use our home-brewed immutable functions that we had to create.
[09:49] Then surfacing initial projects and load projects, we can now in our component, start to go through and, now call load projects, send in initial projects which then is going into the reducer and populating that.
[10:04] The one thing that we had to do that I think is a bit messy still is we had to add an additional step to manipulate the data into something that we could actually use. This is how you integrate NgRx entity into your Angular application.