Reactive Angular with NgRx Computed Data with Selectors
Transcript from the "Computed Data with Selectors" Lesson
>> Lukas Ruebbelke: So things going into the reducer asynchronously was one part of the challenge. The next part of my journey into NgRx is, well, what happens if you need to either connect related models together, or you need to perform some kind of data manipulation before it lands in your component before it's consumed?
[00:00:26] And so, for this, I am going to show you how to do computed data with selector. So we saw some basic selectors in two modules previous. But we're going to extend that just a little bit, and I'll show you how that you can take a selector and do some combinatory kind of logic to produce a new and interesting data structure that you can use to suit your needs.
[00:00:54] So I think we're done with actions effects, and so what we'll do is we'll go into our reducer.
>> Lukas Ruebbelke: Or rather, let's go into the top level reducer.
>> Lukas Ruebbelke: And let's start to build this out.
>> Lukas Ruebbelke: So one thing we haven't done is, we've been working with the collection in terms of create, read, update, and delete.
[00:01:25] But we haven't done very much around the selected Project ID. In other words, when you select it, not a lot is happening at the reducer level. So just internally, we're still managing it in the component. But let's promote that, and I'll show a use case for using computer data and a selector.
[00:01:48] So export const, we're gonna call this selectCurrentProjectId = createSelector.
>> Lukas Ruebbelke: And then within here, we're going to obviously do selectProjectState, and we'll go with fromProjects, getSelectedProjectId. So we're pulling this in from a rehearsal, just taking state and giving you the SelectedProjectId. So if we go back into our reducer, now what we can do is we can say, export const.
[00:02:37] So we're going to just get, well, we have the ID but how do we get the project that it's associated with? So selectCurrentProject,
>> Lukas Ruebbelke: = createSelector.
>> Lukas Ruebbelke: From here, we're going to selectProjectEntities, and we'll go selectCurrent,
>> Lukas Ruebbelke: ProjectId, and then we're going to add in a third parameter here.
[00:03:29] So one thing that I'll point out is if you click in here and you look at this type script definition, this is a very, very overloaded function. I mean, it just goes on, and on, and on, and on. In other words, you can pass in, I don't know, where the madness stops, but at least up to 8, which I would say, if you need more than that, you might have an architectural problem.
[00:03:57] But it will take one, two, three. And so, the third parameter is going to be a function, that is going to essentially be the results of the previous parameter that we sent to it. So projectEntities, and projectId.
>> Lukas Ruebbelke: And from here, well, let's do, we can keep this pretty simple at first.
[00:04:28] We can return projectEntities[projectId]. And so, this is the advantage of kind of breaking these out into IDs and entities is that it takes, this looks this up in almost real-time. So now, we have selectCurrentProjectId and,
>> Lukas Ruebbelke: We'll go into our barrel up here.
>> Lukas Ruebbelke: Make sure I pull this from the right place.
[00:05:10] I make a lot of mistakes but I don't make a lot of them over and over. Now, we'll go into our project component. And what we can do is, right now, currentProject is, well, we're just dealing with this object. It's essentially scoped to be presentation state. So we go, currentProject, observable stream,
>> Lukas Ruebbelke: And,
>> Lukas Ruebbelke: Go here. And so, I'm gonna leave this here for just a moment. Actually no, let's just take that off, why not?
>> Lukas Ruebbelke: And then, you can go here, this.currentProjects = store.pipe(select).
>> Lukas Ruebbelke: And then, we just pass this selector in.
>> Lukas Ruebbelke: And make sure that came in from the right place, selectCurrentProject, from core-data, thank you very much.
[00:06:26] And I think we're good here. Now, let's go into our projects component. We'll update currentProject to use the observable stream version, and then we'll go async.
>> Lukas Ruebbelke: And now, what we need to do is make sure that in our component that,
>> Lukas Ruebbelke: We'll need to update these, because we're no longer handling this at the presentation level.
[00:07:08] So when we want to select a project, we'll go this.store.dispatch, and let me just make sure that in our action object that we have, Project selected right here. SelectProject, and so,
>> Lukas Ruebbelke: There we go. So everything is here, so we should be able to new,
>> Lukas Ruebbelke: SelectProject, and we'll go project.id.
>> Lukas Ruebbelke: Now, what we could also do is in the resetCurrentProject,
>> Lukas Ruebbelke: Is we can just go null.
>> Lukas Ruebbelke: I believe this is,
>> Lukas Ruebbelke: Everything that we need, all right. So,
>> Lukas Ruebbelke: I see some crude lines, let me just fix that out. Looks good.
>> Lukas Ruebbelke: And when we select this, that if we go into our dev tools,
>> Lukas Ruebbelke: That we're dispatching the ID. But then, when we, so every time the ID changes, so notice it's selecting, that ID is changing on the store, which is then triggering in the selector. If go down here,
>> Lukas Ruebbelke: I'm just going to return this.
>> Lukas Ruebbelke: We'll do SELECTOR!, and we'll just go projectId.
[00:09:29] Save this,
>> Lukas Ruebbelke: Go to Console. Notice that as I select this down here, every time I change it, it's changing at the store, but then it's firing the selector. And so, it's basically, hey, something changed, let's go ahead and trigger this and do this cascade of the selectors that you've composed together.
>> Lukas Ruebbelke: So now, the kind of the final piece to this, is if we go into our project component,
>> Lukas Ruebbelke: Let's take this out,
>> Lukas Ruebbelke: And instead, let's move this into our selector. And so, we can do a couple things. I could put it right in here, I could put it in like, a constant file or something.
[00:10:25] I'll just set it near the selector so that we know what we're working with. And so, now that we have kind of this empty project, we can now say, well, for instance, what if you want to reset the project? So inside of this, we can do, well,
>> Lukas Ruebbelke: Is our projectId?
[00:10:52] If so, return the appropriate project. If not, then return emptyProject.
>> Lukas Ruebbelke: And so, this is kind of a nice way to say that I'm going to control and I want to compute on the fly, depending on the current selected project. If it's either null, so there's no selected project, or there is a selected project that you can dynamically compute and determine what is going to be fed into your component.
[00:11:29] What's interesting about this, is if we go back to the component, that the component itself is like, were starting to move these concerns out. And so, the terms of like, does it have an ID or not? Well, empty project or full project? Well now, the component, it doesn't really care about that.
[00:11:51] It's just going to display whatever you feed into it. And this is where we want to move is to have very thin components where a lot of these things of like, you get an empty object or not, whatever is removing further up. Does that make sense? Another thing that we could do just hypothetically, for example, if you notice here that we have like, we're getting all the customers.
[00:12:25] And customers is coming, so this is all coming from the REST API. And so, if we look at this.
>> Lukas Ruebbelke: Now we have a customer, and if we go down to the project, that the projects have customer IDs. So wouldn't it be cool if you could actually take the customers and get the project ID.
[00:12:48] So I'll just real quick, take a second, and show what that might look like.
>> Lukas Ruebbelke: So const selectCustomerProjects, or I would actually do CustomersProjects cuz it's like, many the merrier, cuz that what it feels like it should be. And so, in order for this to work, what do we need?
[00:13:14] Well, we need selectAllCustomers, what else? selectAllProjects, and then we need to put them together. So we'll go customers, projects, and then what we're going to return, is we'll go object or rather,
>> Lukas Ruebbelke: Return customers and we're gonna map over this.
>> Lukas Ruebbelke: And what we want to do is return a new,
>> Lukas Ruebbelke: Object that is made up of,
>> Lukas Ruebbelke: All of the customer properties. So we're just gonna take that, expand it out. But what we also want to do is go projects, and from here, we're going to go projects.filter project,
>> Lukas Ruebbelke: And we're just going to return any project,
>> Lukas Ruebbelke: ID that matches, or rather customerId that matches customer.id. And so, if you are going to do something like this, this would be very kind of specific to what you're doing. But we're basically saying, get all the customers, all the projects, we're mapping over the customers. And we're saying, for every customer, we basically wanna create a new customer object, but we want to add in this additional thing here.
[00:15:37] And that is, loop over the projects and give us just the projects that map to this customerId. And so, when either one of these would change, then it would re-compute and return that particular data. So you can get super fancy or you can do something that's just simply, I want to control when I select the project that it's either going to be something or it's going to be an empty object that I can then use to create.