Redux, and by extension ngrx, generally promotes a normalized data structure with each distinct entity in the domain model being segmented into its own reducer. This is all well and good but what happens when you have two entities in your application that are related and you need to display them together as a single data structure in your application? Outside of asynchronous operations, handling relational data can be one of the most challenging aspects when learning ngrx. Not only can we use selectors to query state from our store but we can use them to dynamically compute data on the fly as it is consumed by our components. By exposing our high-level selectors at our top-level reducer this makes it easy to combine selectors from related entities and transform and return data to our components. In this lesson, we will start off with a simple example of how to use a selector to produce computed data based on a condition and then move on to how combine related entities in a single selector.
Developer: [00:00] Once any developer has learned how to work with asynchronous operations with NGRX via effects or the persistent data library, the next challenge that I believe they're going to run into is how to handle relational data within their application.
[00:19] If every distinct entity goes into its own reducer, what happens when you have related models, such as you have a user that has projects? How do you stitch them together so that, in your application, the user can then view and see, "Oh, here's the user, and here are the projects"?
[00:39] The answer to that is you do that via computed data via selectors. A selector can take any number of parameters as well as other selectors as parameters into that selector. They are composable.
[00:57] We're going to start out with a simple example of allowing or returning the current selected project based on the entities and the currently selected ID. From there, then we'll expand on this. I'll show you how to compute users and projects and combine them together so that we could display them in our application.
[01:22] Let's go ahead and create our first selector. This is just going to be called select current project ID. We will call create selector. This is going to take two parameters, the first one being select project state and the second one being, from projects, get selected project ID.
[01:51] What we're going to do just before we get started with this is we're going to hop into our project component. We're going to take this empty project object. We're going to paste it into our top-level reducer here.
[02:10] We'll see why we're going to do this in just a moment. Little hint, it's going to have to do with how we're going to compute data or determine what data we're going to return when we want to select the current project.
[02:25] The very next thing that we're going to do is we're going to define our select current project selector. This is also going to take a couple parameters. The first one is going to be select project entities. The second parameter that it's going to take is getting the current project ID.
[02:59] Up to this point, we've only used two parameters. If we click on the create selector or command-click on the create selector method, we can step into the interface. You can see that it accepts a ton of parameters with that last one being essentially a function that's going to create and return a result. It's a result function.
[03:22] In here, it's going to take two parameters based on the parameters in the parent or selector function, so project entities and project ID. Then from here, let's go ahead and build this out. We're going to use the project ID as a key on project entities to return the appropriate project. We could stop here.
[04:02] This is where our empty object comes in. We want to detect is there indeed a project ID? It may be null. If there's no current selected project, what we want to do is return the empty project. When somebody calls select current project, then if there is no current project, we're going to return the empty project. In this case, it appears that order does matter.
[04:28] Let's go ahead. Let's cut these. We'll move these down to the bottom of our project selectors. We're just getting a TSLint air there. Now, in our barrel roll, let's export the select current project selector. Then let's hop into our project component. We'll delete that empty project because we no longer need that.
[05:01] Let's convert the current project into an observable stream. Now let's update our template. Current project, we'll add that dollar sign. Then we'll just add on the async pipe.
[05:24] In our constructor here, we need to also select our current project. We're going to go this current project and then store.pipe select. In this case, we're going to say select current project. This just is a very nice way to encapsulate essentially a query to our store.
[05:58] Now that we have updated the application to pull the current project from the store, we need to update these particular methods. Instead of setting it here at the component level, we're going to dispatch a select project action. We're going to pass in null for the selected project ID.
[06:27] Then, within our select project, we'll go this store dispatch. Here, we'll go new select project. We'll just pass in the project.ID as the payload. If we hop into our application, we can see that it is indeed working. Then if we look in our dev tools, we can see that as we selected, that we have the IDs coming through.
[07:07] Let me show you one more example as promised. This is going to be a selector that's going to allow us to get all customers and then for every customer, grab the projects that belong to that customer. This is, in my opinion, the third major milestone to understanding NGRX. That is how to combine data models on the fly.
[07:33] We are going to create a select customer's projects create selector. This is going to take a couple parameters as well. The first one is we're going to select all of the customers, select all customers. The second parameter is going to be select all projects. Then, a result function that's going to take customers and projects.
[08:03] What we're going to do here is we are going to map over our customers. For each customer, we're going to return a new object. Inside of this object, we're going to just expand out the customer's properties using the spread operator.
[08:30] Then what we're also going to do is create a projects property. We're going to loop over the projects array and filter out all the projects that do not belong to that current customer. We're just matching up customer ID to customer.ID. This is how you would do computed data on the fly by using selectors to select the pieces that we need.
[09:01] Then we can iterate over that and return a customer data structure that we need as well as this will be recomputed any time any of these parameters change. Whereas if nothing changes, then it's memoized. It's just going to return the last known data structure. This is how you do computed data in NGRX.