Local state management with @ngrx/component-store by Alex Okrushko || Angular Conference
8K views
Nov 16, 2023
In the session of Angular Virtual Conference by Alex Okrushko learn about local state management with @ngrx/component-store. C# Corner - Global Community for Software and Data Developers https://www.c-sharpcorner.com Conference Website: https://www.2020twenty.net/angular
View Video Transcript
0:00
Awesome. Thank you so much
0:02
Yeah, I will wrap up only after I tell everything about the Component Store
0:06
Actually, I won't have time today to tell everything about the Component Store. I'll touch on a lot of things, and hopefully there will be more questions
0:14
and further presentations on it as well. So a bit about myself
0:21
So I'm Alex Akrushko. You can find me on Twitter at Alex Akrushko, and my DMs are open
0:27
So if you have any questions about today's presentation or anything like that, feel free to shoot me a question and I'll try to answer it
0:37
I'm working on the Firebase console, currently at Google, as I was already presented also in the InterX team
0:45
And I'm very happy and proud to have the Angular Toronto community
0:50
of Angular developers here in Canada. So I'm organizing that meetup. and I'm also very proud to be part of the fantastic people at the in-depth.dev
1:04
which is a fantastic community. If you're not subscribed there, make sure you subscribe
1:08
It's really awesome. All right, so that's a little bit about myself
1:12
So what are we learning today? What is this NGREX component store
1:17
Well, it's a state management library, right? And you might think, hold on, hold on
1:22
Doesn't NGREX already have a state management library? Well, yeah, there is a JREX store, right
1:29
And this is a global state management library. So this component store is completely separate library
1:35
It doesn't depend on it. It's self-contained. It's basically like one file with an extra operator
1:41
So it's very tiny. It's very performant as well. So this is the one that I'm going to be talking about today
1:49
But state management. Okay, so what is the state management thing we're talking about
1:53
Well, we have a state and we need to manage it. And before we dive into those things, we want to understand what is the state
2:03
What is the state thingy? And, again, there are multiple ways to slice this and present this
2:11
but my mental model about this is the following. We have a server and we have a web app, right
2:18
The server keeps a server state, no surprise there. as the backend state
2:23
And this is basically a source of truth, right? So this is all the state we save, it's there
2:30
Then on the web app, we have a bunch of different states. There's more, but we'll talk about these four today
2:37
because they are interesting in the components or real. And we'll start with the persistent state
2:43
What is the persistent state? And I'll show some examples based on the Firebase console
2:48
So I'm working on it and why not, right? All right, so the persistent state is the state
2:55
that we get from the backend. It's a piece of state, it's a snapshot of some of the state
3:02
that backend had at some point when we requested this. So this is a persistent state that we get
3:07
within the application. We also have, well, URL state or router state
3:12
sometimes people call it, and this is to no surprise, is the state of the URL
3:17
Sometimes it's router state, so it has a little bit more things like data and stuff like that
3:22
For what we care about is the URL itself. Then there is a client state
3:28
This is, for example, something that would persist to the back end
3:31
For example, which tab is open right now? Here is a sign-in method tab is open
3:37
This is a type of state that stays within the app itself. It's not going anywhere, but the app itself is aware of it
3:43
So it could be shared around, and it's aware of the state
3:48
And then the last one is the local UI state. This is the state of this enabled button, right
3:53
So enabled button by itself knows that it has two states, basically on and off
3:58
It doesn't care what it is. It's very, it's a dumb component. So it's very included within that component
4:05
It's not shared everywhere. It's passed, you know, should it be enabled or disabled
4:09
maybe by an input and you know, when you click it, it communicates the output
4:13
So it's basically like a very common component or a very localized UI state
4:18
All right, so those are the four states we have, and the typical problems that we have
4:25
within our application is how we can synchronize all of those. So this is where a lot of the challenges happen
4:31
How do we call APIs, how we persist the state, how we read it, how we update it, and all
4:36
of these between. We change the URL, the client state needs to change, you know, a specific tab needs to
4:41
be shown. Some APIs need to be called. So all of these synchronization is what the state management is all about
4:52
So how is ComponentStore managing this state? Well, ComponentStore, just like any other thing or any state, it needs to do three things
5:06
Basically, first is we need to be able to read the state locally
5:11
And then we need to write the state. And then we also have some side effects, which we'll cover in a second
5:18
And we need to organize and manage them as well. And if you're familiar with the global and direct store, you probably have seen the create selector, create reducer, or create effect
5:31
Those are like the factory functions to either read, write, or create some side effect and manage that
5:37
Well, as you might be, there's no surprises, I guess, ComponentStore also has a way to read it, and we call this a select method
5:48
There's a way to write the state, and we called it an updater, like updates the state
5:54
And in fact, well, the side effect is just an effect. There are a few more methods, but we'll be covering them today
6:02
And even some of those methods we see only to be used in a specific basis
6:10
For example, like get, we only want it to be used in effect, so it's even protected and not publicly available
6:16
Anyhow, so those are the APIs that we have for the component store. Component store in itself is supposed to be a better version of a service with a behavior subject
6:30
So this is a push-based service that has the state and reacts to things
6:36
And it's a better version, I say, because we also have side effects to manage all of those rates conditions, which we'll get into in a second
6:44
So how do we write the component store? The typical way I see components to write it written is first we need to tell it which kind of state it operates on
6:54
For example, we have statuses of different statuses on saved, error, loading, and outloaded
7:00
For example, we have an author state, right? We have this ID, name, some books that are associated with this author
7:06
books, IDs, and in the status of this API to load the author state
7:12
All right, so this is the interface that this is the state that we'll be working with
7:17
And again, ComponentStore, unlike GlobalStore, is supposed to be very localized. So the scope of it is very different
7:24
So the typical way we would write it is we'll create an author store
7:29
and we'll extend the component store. So component store comes with all the tools we need
7:35
It comes with a way to read the state, it comes with a way to write it
7:38
it comes with a way to create side effects. But we need to add some meat to it
7:44
add the logic how to do this. So what exactly are we doing
7:49
So that's why we create this author store, which just extends component store
7:54
which is a tool box. All right, the first thing we can do is here I'm initializing the state
8:00
You can do it or you can do it later on up to you. There are ways to do it, which we won't cover today
8:05
But here I'm initializing the state. I want it to be, for example, on saved
8:09
and having some default values All right So now we are to read How we read it Well we read it with a select and it very simple So again select has a callback method but basically is a prescription
8:24
We have a state. How do we extract something from it that we care about
8:28
So again, all of these read, writes, and effects will all be answering the how question
8:34
So how we select. So we have a state that has, you know, IDs, names, statuses, and stuff like that
8:39
we want to get a name out of it. So this is how we do state and state.name, we get it back
8:44
And this returns us, as you can see by the dollar sign there
8:48
it's an observable of strings now. All right, so we make it every don't
8:54
so just nobody changes it by accident. So again, this becomes a push-based service, right
9:00
So our author store is a push-based service. So how would we use this
9:04
How would we use this name? So we have our component, like author component
9:09
that has a few things. And let's see how we would use this author store, right
9:14
The store that has the business logic that's associated with changing the author
9:19
So first of the important thing is we would put author store in the providers
9:24
Why don't we put it there? Well, when we put it there, that means that this author store
9:31
would be tied to the lifecycle of this component. So for example, when the component would be destroyed
9:38
this store would be destroyed. Another thing is if there would be multiple of author components
9:44
each one would have their own unique version of the author store because it's really this now
9:49
this we're tying the store to a particular component. That's why we would provide it in here
9:56
Okay. So then in the injector itself, we would do the private read-only author store
10:02
and we inject this author store. So now it becomes available to our component
10:08
All right, so now this name, well, it's very easy, right? Because it's a property on the store
10:14
And now we can extract it and use it in template or anything. And it's very push-based
10:20
So whenever anything changes the state, it gets pushed to this name
10:26
and our component can react to this. All right, so this is how we select it
10:31
How can we update it? How can we update the store? All right, so let's look at that
10:38
So we would write it with the updater function and a method
10:43
And this method, again, tells us how we change the state. So, for example, given the state, the previous state that we have, which is a first argument, author state
10:53
and given the new status, some kind of new status that we get into this update status
10:59
we would show how we would immutably change the state to this new one
11:04
So, for example, we would spread the previous state. So anything, any properties that we had, like ID, a name, they all stay the same
11:11
And last one is status. We would override that status. And this property, it's not actually a property
11:18
It is a property, but it's a callable property. So you can think of it as a function
11:22
And if you can see below there, this update status, we pass a value to it, loading, right
11:29
We pass a status to it. And what it would do, it would change internally the state to loading
11:34
And if we are reading it and selecting it with a selector somewhere, it would right away react and pick up this new version
11:43
And down below, for example, we have update status to load. So see, those are how, like, a very comparative function that you can call and basically update the status very easily
11:54
And the whole thing reacts to this. Awesome. So this is how we write them
11:59
How would we – so the next one is an effect. Effect is a little bit more involved, so we'll get to this
12:07
But I feel it's such an important part of a component store
12:11
And actually, in the Indirects, the global store, there's Indirects Effects, which, again, I think is one of the most crucial parts there is
12:21
because it allows us to handle those race conditions that we never test for
12:27
it's hard to identify, and we sometimes don't even think about it. If we have just a method on the service that does and calls the HTTP call and gives like this zero back, you know, if the user clicks and invokes that method on the service multiple times, now we have a race condition
12:45
And if we're updating values, now we're in big trouble. All right
12:49
So that's what effects are for. Effects are for handling asynchronous things, but also at the same time controlling all those race conditions
12:58
All right. So let's look at this one. So we have a get author effect
13:03
And what it takes, it takes an observable of IDs, right? So what are those IDs
13:09
Those are author IDs that we would be pushing into this one. We'll see how exactly
13:14
But think of it as our callback gives us this ID and asks us, how would we handle that stream of IDs that we have
13:24
All right. So the first thing that we would do, we would filter. We ignore that for now
13:28
and then the next thing we would update the status. Now we consider it loading, right
13:33
So now if we select the data about the status, we would know that it's loading
13:40
So this is what a tap operator basically is another side of it. The next one is super critical
13:46
So this is where we handle our race conditions. And here we use a switch map
13:52
If you know there's a switch map, merge map, exhaust map, and some cat map all have different ways to do it
14:01
So they all handle different, the incoming stream of values and how you would handle them
14:08
Would the queue, would the switch map drop any observables it's working on
14:14
or can cat map, you know, we'll queue them, and exhaust map will drop the incoming ones
14:18
So basically those are strategies, how we handle those race conditions. So here we would do is we would call our author service and we would get that author by a specific ID
14:33
Once we have this result, we will call the tab next. And we first of all set the author
14:40
Set the author is an updater that sets the author into our state
14:45
And then we have another update status, which is another updater. And now it sets it to load
14:50
If we have an error, our update status was set to error, right
14:55
And somebody else, you know, somewhere can pick it up and show the error message
15:00
And one last thing is, if there is an error, right, we basically need to catch this error
15:06
and transform it to empty before we flatten this into the main stream
15:13
All right. So those are the side effects. So we can select, we can update, and we can close those side effects
15:18
Side effects is basically the orchestration of all the asynchronous stuff we do, calling the services, handling risk conditions, and stuff like that
15:27
All right. So how will we call this effect? Well, we can call this effect, for example, in the input of author component
15:36
For example, this author component that takes an ID of an author ID and says, hey, you know, this is what you get
15:43
Do whatever you want with it. And what we do instead is like, okay, author store, give me this author
15:50
And it pushes this ID into this. Notice it's not an observable that I'm pushing it
15:56
I'm actually pushing a single value into it. But every time I call this get author, right, for example
16:04
if a parent component changes mine and pushed me another ID right after a few seconds
16:10
while I was still retrieving the first author, it would be pushed into this observable stream
16:17
that we saw above and it would say, oh, you know what, we have a new ID
16:20
we don't care about the current one, drop it and start another request for the get author If it was just a service with a method it very easy to overlook those And we could have gotten the race condition where the first request actually gave us a result second
16:39
And we could have had a wrong author. Those are like those tricky race conditions that are really hard to get right
16:46
And the component store is one of those purposes is exactly that, giving you the tools to work with in a very nice way
16:54
All right, so this was the effect. So how is it different from the global store
17:03
Well, there are a lot of differences. We'll cover some of them. So, for example, in the Interact Store, it's really meant for the global state of the application
17:15
And as such, it scales a little bit differently. We split things by separate files because it's supposed to be a lot of things, right
17:25
It's a lot of global things, and we split them by almost like responsibility there
17:30
So selectors are separately, reducers are separately, effects are separately, and they're all in separate files
17:37
And Jerez Component Store is supposed to work with the smaller data sets, smaller effects, smaller state
17:43
and the state for that particular component only, or its children. So it's all in one single file
17:50
So this is like one of the first difference. So select our data and, in fact, are in the same file and they should be in the same file
17:57
There are some additional selectors in a separate file sometimes, but we won't cover that today
18:03
All right. So what are the differences? Well, in direct store, even though things are all separate, it's a one single state for the entire application
18:14
Right. So it's one single. Even if we do four features, they are patching the one single object of immutable state
18:22
Introducer there is responsible for its own slice. That's true. But it's still a one single state
18:28
While in Injects component store, it's not the case. We have multiple independent states
18:34
And this is why it's super, super crucial to not duplicate the state, to really understand who's the owner of what kind of state
18:44
where should you push component store in the component tree should you have it
18:51
This is super important. So, again, there are multiple independent states. They're not talking to each other, and they shouldn't be even talking to each other
18:59
It should be a clear ownership of the states. Okay. So those are those two
19:06
And lastly, Interact's global store lives for the lifetime of the application, right
19:12
So we keep the status there all the time. While the Indirects Component Store, the way it was designed is it should clean up after itself when the component containing it gets destroyed
19:25
So this is, again, quite important ways to distinguish this. So where should we use Component Store
19:37
Well, I'll cover a few cases today, but there are a lot of use cases, as we discover as we start using it
19:44
Within Firebase, we've been using ComponentStore as an usual version for almost a year now
19:51
No, actually, yeah, since like November of last year. So there are a lot of use cases
19:55
We'll cover some of them. First of all, let's cover like a module state
20:00
Then we'll go into the shared common component state. And this is actually the state that basically gave birth to this component store within Firebase initially
20:14
Then there is a complex independent component state, which is kind of very related to the module state
20:21
Reactive local UI state as well. And the last one is provided in root
20:27
I put the warning sign there. So again, component store is designed to be used with the component lifecycle
20:35
You can, in theory, provide it in root, and there are some cases for it, but basically be very careful with that
20:43
All right. So let's start with the module state. What is this module state
20:48
It's not an NG module state. It's not JavaScript module state. It's a whole different other module state
20:53
What we call the modules is something that's completely independent within the application and self-contained as well
21:01
So, for example, we have, you know, multiple in Firebase console, we have multiple pages, right
21:09
We have a page for authentication. We have a page for database. We have a page for, you know, storage
21:15
And each of those pages open and they're almost like completely independent statewide from the rest of the application
21:24
Yes, some of the state does trickle down from the global indirect store, for example, like a project number that you're working with
21:33
And in a few cases, there are connecting to some other modules, but mostly it's a self-contained piece of a component tree
21:47
All right, so that's the module state. Let's look a little bit into this authentication one
21:54
This is the one that I was working on as well, so it's kind of very familiar for me
22:00
And I'll try to explain what exactly I mean here and how we would use the Component Store in this example
22:06
So when you click on one of these providers, like this one, email password, it opens up
22:12
So this is a provider page. You can do the same thing and click on phone provider, and it would open up the same provider window
22:22
So basically, those are providers for authentication. And we also can have like a third party providers like, you know, Google is a third party auth provider or even, you know, Twitter
22:33
Like those are all providers and can think of them as a provider component
22:38
Right. They're kind of independent. All right. So we have those providers component
22:43
But then, you know, they're not just buyers themselves. They have to have a parent component
22:48
And in this case, this is their parent component. It will be like an identity provider
22:53
So it contains a bunch of providers inside it. But even that, if we scroll a little bit down, there's more happening
23:00
And even this page by itself, you know, it's a tab. It's a provider's tab
23:05
It has quota at the bottom. So basically, this is our component tree
23:11
Again, very simplified, right, how they contain each other. All right. But then we have also other tabs as well, right
23:17
So our authentication page is actually bigger. There's users, there's templates. It's quite bigger
23:24
We have all of that. And let's just, for the picture here, we'll put it as well
23:30
So there's a users tab, users table, some row, templates tab, and we have like authentication index space
23:37
So this is the one that contains the whole module. And the way the APIs are structured for authentication, for example, is that we have API to get some config
23:49
and config covers all of these users. It covers providers, covers some of the template stuff
23:56
So it's API that just goes and gets a lot of things at the same time
24:01
Because of that, we have to have a way to store that data
24:06
And what we decided to do is to have the auth config store. And it's basically the one
24:10
that is tied to the lifecycle of the auth page. And then it shares
24:17
once we get the API results, it shares all the data throughout the different components
24:23
and they can basically talk to the store to get the user data
24:27
to get some of the quota data, to get some of the providers or templates or other stuff
24:32
Sorry, Mark. I'm busy right now. Yeah. So anyhow, that's why we have it all in this way
24:39
and this way it actually owns the state on that page right So this is where it covers it So off owns all of the config
24:50
and it's not shared anywhere above it. So it's only shared here
24:56
Sorry, can you close the button? Sorry, I have
25:09
Saturday, all right. Okay, so getting back to this one. All right, so we have a config store that shares all of this data throughout
25:18
Moreover, we have another store, which we have an onboarding store that's also is on page
25:26
So what I wanted to show here is that there could be multiple stores at the same page now
25:31
it could be stores below this, it could be stores above it
25:35
but you have to have a clear understanding of at which level we actually, you know, the state is owned, right
25:45
So it's owned at that particular level. All right, so that was the module state, and then the shared common component state
25:55
And this is actually the one that, you know, kind of gave birth to this component stuff
26:00
We had a problem where we had quite a complex component. So for example, we have an app, right? We have multiple pages, right
26:07
the different pages, different things happening. And then some pages have different components and stuff like that
26:12
So just have that. But then we have a common component. It's a shared component
26:19
It's a component that's dumb. It talks to the outside with its parents through the inputs and outputs
26:28
So we have those common components, the different pages. But that component is quite complex, and you could have it with children too
26:35
and its state could be very complex. And then you might need to call the services directly as well
26:42
maybe for validation or other things. But we wanted to have that state and basically have that state contained
26:52
self-contained somehow. And we didn't want to push it to the indirect store
26:56
because it didn't have any place. It shouldn't be in the global things
27:02
It's not really shared anywhere, right? So component component is actually the one that owns it
27:08
So what we ended up doing is having those components stored, right
27:11
And as you show as well, when you provide that component store, in this case, it would be individual and unique for each of those components
27:21
So this one was a very interesting case, and this is the one that basically gave birth to this component store
27:29
All right, so this is a second. Third one is a complex independent component state
27:34
So it's kind of like a module one. So we have multiple pages
27:38
We have a component A, which, again, owns the state. It's not shared anywhere
27:43
But components B and C, one that you have some of it, maybe transformed in a transformed way
27:48
D might want as well and all that stuff. So we would – and then we also wanted to have everything cleaned up
27:54
when we move to page one, for example. So, again, we would create a component store that's tied to the lifecycle
28:01
of the component A in this case. It's the one that owns the state
28:06
And then it's basically shared through those selectors down. And if those components need some data to be changed
28:12
they call either those updaters or they call those effects and push some new data into it
28:18
So it's kind of like a case of a module state as well
28:24
if you can think of component A as a module. All right
28:30
The other one is the reactive local UI state. And this is what Michael touched on as well
28:37
For example, should you go zone-less, you can have a local UI state that's reactive
28:43
And if it's just the property that you change, you need to know how to change that or whatever
28:52
that you need to update that and all that stuff. So you can use those push pipes and the rest of the things
28:59
So you need to have your local state reactive as well. So component store can also be used for that
29:07
And then the last one was the provided root. Again, you have to have a clear, clear separation
29:14
If there's a little bit of this state that's not shared anywhere much, right
29:19
and it could be used, but it's self-contained state, doesn't have a lot of inputs from other places
29:26
then it's kind of okay. But the first instinct should probably, you know, if you have something really global, the first instinct is probably should be in the InterX store itself
29:36
By the way, a component store does not depend on the InterX store, the global one
29:44
It's completely self-contained. It's supposed to be a replacement for the service, you know, with some cash, some state, a Polish-based service
29:55
I have written the design doc for it. Search for it, component store design doc
30:02
You'll get on this. And one of the last things I want to say
30:08
a huge thank you for these people. Kevin Elko, he's my teammate at Firebase
30:15
He's the one who started the component store internally. And then, you know, talking with Indirect's team
30:21
we decided to push it upstream, basically, and share it with everybody
30:28
I've had these questions. I have this, you know, multiple instances of the same component
30:34
that's fairly large and have self-intained state. How can I do this with NGREX
30:38
And the answer was like, well, you can kind of put by index
30:42
by in the global store, but they're like, well, they don't do it
30:46
and all that becomes clumsy. So, yes, this is why it was created
30:50
I also would like to thank EngerX team, Brandon and Tim. They've been reviewing this and providing feedback a lot
31:01
So thanks a lot for them. And also Nicholas Jameson, who was also crucial
31:07
even one of the custom operators that we have as part of the Component Store
31:12
which I didn't cover today. It was actually written by Nicholas. He's a fantastic guy
31:19
If you don't follow him, you should. If you didn't read his blog, you missed like a ton
31:24
It's one of the best blogs there is. So I'd like to really thank those people
31:29
They're fantastic. And lastly, so Component Store, I'm working on a guide right now
31:36
There's not a lot there right now, but hopefully soon. And Component Store should be released with the upcoming InterX V10 release
31:46
which again will be happening fairly soon so the docs will be
31:53
updated by that time and you can right now already go and play with the nightlies
31:58
builds and if you want any feedback if you want to have any feedback for the component
32:05
store I'll be very happy to listen but looks like we're like where it's getting
32:10
and so far I had a lot of fantastic feedback. So that will be
32:14
it from me. Thank you so much. If you have any questions, again
32:19
reach to me on Twitter is the best way, and I'd be happy to
32:24
answer them. I'll already go and play with the nightlies builds, and
32:30
if you want to have any feedback for the component store, I'll be very happy
32:36
to listen, but it looks like we like where it's getting, and so far I had
32:42
a lot of fantastic feedback. so that would be it from me
32:46
thank you so much if you have any questions again reach to me
32:51
Twitter is the best way and I'd be happy to answer them