Lesson Description
The "Writing State with rxMethod" Lesson is part of the full, Advanced Angular: Performance & Enterprise State course featured in this preview video. Here's what you'd learn in this lesson:
Alex demonstrates using exhaustMap to control cart updates, compares it with concatMap and mergeMap, and shows how to optimistically update state while handling API responses and errors.
Transcript from the "Writing State with rxMethod" Lesson
[00:00:00]
>> Alex Okrushko: Now let's add another method. Let's add add to cart RxMethod and this one takes event ID as an input. So we once again can do the pipe and then do a bunch of things here. Pipe allows us to group multiple operators. Let me just show you this first. Okay, so we can have, in this case, we can do exhaustMap. So exhaustMap is our choice here, and we're saying that, hey, if the event ID, if I'm adding something to the cart, just ignore anything else.
[00:00:53]
And again, this is up to you how you want to handle it. Do you want to sequence all the additions? You can do that. That would be concatMap. Do you want to have non-controlled additions with mergeMap? That's what we had before. We see it's not really working out well. Right, so we can say exhaustMap for now. For example, if you click to add it, just don't do anything else in the meantime.
[00:01:23]
If you click again, it's not going to send the new one to add it. All right. So again, up to you what you want to choose, but you have a choice, and having a choice is better than not having a choice. That's hopefully, you know, of that. All right, so in the exhaustMap, we're going to do a few things. So we're actually going to set pending within the exhaustMap.
[00:01:51]
So in the exhaustMap, we can, the first thing we're going to do here, we're going to set it to pending in the exhaustMap, so we can have no. We can do. By the way, right now I'm using the same request status for loading and adding to the cart. They typically, well, they're actually two different requests. If you want to do that, you can have two different functions for this, right?
[00:02:17]
You can basically prefix your request status with something like loading and whatever, if you have two, right, and you do want to differentiate. In my case, it doesn't really matter because loading and adding the cart are never at the same time. I'm going to reuse the same network request status. Okay, so now patchState, this is our basically take on the request that was before httpResource was ever available.
[00:02:53]
We had the network request status like ways to deal with loading, pending, whatever state for many, many years already before. All right, so let's patchState with pending. So first of all, we'll say, hey, not only with pending, but we're optimistically going to increase is there as well. So we're going to increase, let's see, store. We're going to increase our tickets here.
[00:03:31]
So we are optimistically adding the value. So let's do that. In updater, I can get previous state here. And what I'm saying is like, okay, now we had the previous state. Let's return a new object, where the ticket IDs are, what, what are they? We're optimistically adding it here. We'll spread the previous ticket IDs state ticket IDs, and then I will add this event ID.
[00:04:12]
Right, so now we have this, and so that's updater number one. So we're updating ticket IDs based on the previous state. And then the second thing I'm going to do is I'm going to set the state to pending. All right, so that's before we do anything. Now it's time to do an API call for this. So, we're going to return our HTTP call. HTTP, http.post.
[00:04:48]
What's coming back doesn't really matter. Void, let's say. I'm going to post to this ticket URL, and we're going to post this event ID. This is very similar to what we've been doing in this service here. Add the ticket. That's what we're going to do. And here I was just doing it with the error, right, but yeah, it's the same thing. That's what we're going to do.
[00:05:14]
So we're going to post. So you see, we updated optimistically tickets before anyway. We did the same thing here. We just patched the state, updated the tickets as well. Okay, so now we have this post, and once again we have the inner pipe. And once again we have the tapResponse. And once again we have next and error methods. Next. On the next, what we're going to do in the next?
[00:06:04]
I'm going to patch the state. The store, set fulfilled, because we already optimistically patched the tickets. On the error case, this is where we need to do some reverting, right? Error. So let's do this and what do we have? We're going to patchState, our store with, what are we going to do there? We're going to take the current state, and we'll try to find the index of the last ticket that was optimistically added.
[00:06:54]
And so index. Yeah, we can do, there's multiple ways to do it, but basically we need to filter this event ID out. This is one of the ways to do it. Previously we had the filter by index. This one will, here we'll just use splice that I mentioned before as well. But anyhow, that's one of the ways to do it. So ticket IDs, lastIndexOf this event ID.
[00:07:27]
So we're trying to find this item in the array, the last one that we added. Again, if it's not there already, which it shouldn't be the case, but we do not want to try to remove something that's not there. So if it's not there, then just do nothing. But we should have something there. So we're going to do the new IDs, which we are just cloning the ticket IDs.
[00:08:14]
It's a shallow copy because splice is a mutation operation. We could have done state.ticketIds.filter, again, that specific index. Yes, so newIds.splice, splice, index one. Return ticket IDs, which just be new IDs. Right, so we are reassigning the new reference of this array. And then the final one, we can also set the error, is that error, and then like error message or something like that.
[00:08:47]
We could just pretend that this error is a string, so it depends. Right now, errors are coming as type unknown, so you really should check what type of error it is. If it's an HTTP error, then we probably need to have like the message for it, the status from it, right? So there's, I'm just saying, hey, assume that you have a message that is a string.
[00:09:17]
So then I can do this error message, right, because it comes unknown. All right, so now we'll set there. And again, you can see here, oh, by the way, I'm using the pipe, but really this is the only operator that is in the pipe, so pipe is really not necessary because pipe allows us to group multiple operators. So I can just remove this pipe altogether and just leave exhaustMap as what it takes.
[00:09:44]
Yes. Does signal store enforce immutability like global store does? Does it enforce immutability? No, it doesn't enforce the immutability. It's a little bit of lighter weight approach. Yes, it doesn't, because it doesn't really know because you patch the state with things. So is it really necessary to create a constant newIds and spread ticket IDs, or if you were to?
[00:10:15]
Yes, it's necessary because we need to create the new instance of this. If we don't do it and just mutate in there, our patchState does not know that the reference will be still the same for the ticket IDs. So it's very much recommended to keep immutability intact. There is, again, this is like really jumping. I'm not going to cover it today at all.
[00:10:47]
But there are multiple different plugins from a direct team that is already available for signal store. We've heard event plugin. There's also entities plugin. I really suggest to take a look into that as well. That's as much as I can go in today. All right, it's a big topic. It's usually two days on its own topic to can really take from all different angles.
[00:11:19]
All right, so now, but they see the pattern. The pattern is still the same, right? We have some kind of operator that, and then we do the post here, right? And we keep the optimistic approach as well that we learned in the intermediate Angular course. All right, so now this is done. One other thing, which I have it here, yes, we do have it here.
[00:11:50]
Look, if we look at our cart service, one of the things it was doing was by itself whenever it's first injected, it would load the tickets. We can do the same thing. I can do the same thing. We can have withHooks, right? And we can do it on our onInit. We'll get our store here and then we'll do the store.load. And moreover, if we want, our cart service had this loadTickets as a private.
[00:12:28]
That's added as a private. So when we return, we'll do the load underscore here. So anything that's underscore is still propagated within the store itself. So my hooks will have access to this. My hooks will have access to this, but the cart store, when it's injected, it's not there. It's just stripped. And it's not even just private or hidden.
[00:12:59]
It just truly stripped. There you go. Now it can load only within the hook. So just one more thing. RxMethod tells us how to do things, right? It's really just a method. Give me an input and I'm going to know how to deal with this. Add to cart, exactly the thing. It knows what the flow would be. And again, if you see this for the first time, it could be quite intimidating.
[00:13:24]
I promise you on the fifth, seventh time you'll write something like this, it becomes very the same pattern with your state, with your computed, with methods. If you don't have a computed, you just can skip it. You'll have some of your custom features that you reuse across different stores, and then with methods, most of them are just RxMethods.
[00:13:53]
And then here I'm calling the HTTP directly. Usually you will inject your service here that wraps the HTTP client. So this could have been, you know, our cart service if it was stateless and it was not trying to do the optimistic update. For example, our other service, which is event service, stateless, right? Delete, create. So we'll just inject that and call that in RxMethod, typically.
[00:14:04]
Here I'm just going directly because our cart service is not, but it, and we can just replace it altogether.
Learn Straight from the Experts Who Shape the Modern Web
- 250+In-depth Courses
- Industry Leading Experts
- 24Learning Paths
- Live Interactive Workshops