⚠️ This lesson is retired and might contain outdated information.

Connect Related Angular Data Models with Computed Data using ngrx Selectors

Lukas Ruebbelke
InstructorLukas Ruebbelke
Share this video with your friends

Social Share Links

Send Tweet
Published 6 years ago
Updated 6 months ago

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.

massimo-ua
massimo-ua
~ 6 years ago

Is this solution (computed data) applicable in case when you have a lot of data and only part of this data is actually used by user (long list or table with pagination)? I mean what if you need to display 5k users with their own projects (10k) and user interacts in the same time only with about from 10 to 100 records. So to use computed data you have to hold all users and projects records in the store.

Lukas Ruebbelke
Lukas Ruebbelkeinstructor
~ 6 years ago

Typically when I run into these scenarios, it is indicative of a UX problem. I cannot think of a reasonable scenario where someone would need to display 5k of anything at a single time to a user. How could a user even process that information? With that said, I DO recommend implementing a pagination strategy for large data collections at the store and only pull in enough data to satisfy the user experience within the context of the business domain.

massimo-ua
massimo-ua
~ 6 years ago

Thanks for quick reply Lukas. We use pagination in our project and in store holds only data that correspond to one page of data. But in my case I decided to pull from backend list of users populated with their projects (every user has property "projects" that holds list of his projects). In that case I do not need to combine data on frontend. But in the same time I need to hold in store separate additional list of projects to have ability to manipulate with their properties. Is it correct (part of projects data is duplicated in store)?

Lukas Ruebbelke
Lukas Ruebbelkeinstructor
~ 6 years ago

Are you normalizing your data?

massimo-ua
massimo-ua
~ 6 years ago

Sorry for such a long list of questions but that is actually what I am asking about. Should I always keep my data in store 100% normalized or there are cases when it is not necessary?

Lukas Ruebbelke
Lukas Ruebbelkeinstructor
~ 6 years ago

I recently read a blog post where the author had seen a significant increase in performance by NOT normalizing their data. This goes back to the class relational vs nosql database conundrum where one is really good at writing data where the other one is really good at reading data. To answer your question, you do not have to follow are guideline or rule just for the sake of being "compliant". Our job is to provide value to our organization and customers and sometimes there are good reasons to prioritize them over "purity". I would just suggest that you fully understand your domain and the tradeoffs you are making and then make the best long term decision for the project. For instance, if you're application is primarily a read-only style application, NOT normalizing your data may be a good choice for performance reasons.

Oscar Lagatta
Oscar Lagatta
~ 6 years ago

Hi Lukas,

are you showing the computed data in the application somehow ? how you manipulate the data returned in the component to display parent child relations?

thanks

Lukas Ruebbelke
Lukas Ruebbelkeinstructor
~ 5 years ago

Hey Oscar, No, that last one was just to show you could build computed data in the selector. As far as actually displaying it in the UI, my go-to generally a nested table or accordion structure.

Wilgert Velinga
Wilgert Velinga
~ 5 years ago

Why are you wrapping a spread operator in Object.assign?

Does not make much sense, because TypeScript already turns a spread operator into Object.assign. See this TypeScript Playground

Markdown supported.
Become a member to join the discussionEnroll Today