Filling the Gap in State with NgRx ComponentStore | Alex Okrushko | EnterpriseNG 2020 #ngconf

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] let us hey auntie kong alexa krischko here and i'm super excited today to present you and your x component store the latest addition to the interx family of state management libraries so my topic today would be filling the gap in state within direct's component store we'll take a look at what problems we're trying to solve with the uh component store what's the service with the behavior subject and where it has its own disadvantages and also we'll try to look at some of the use cases for the component store now before i continue let me introduce myself uh some alexa kruskal i'm currently software engineer at firebase at google i'm part of the ngrx core team i do also organize angular toronto so if you have an exciting topic please reach out to me and i'm part of the fantastic group of people at indepth.dev so one of the greatest minds of all time albert einstein said this once synchronizing state within the application and the back-ends is one of the most complex tasks that developers have to solve well actually uh he was you know with us just before the internet was created so he probably didn't see this problem however i'm sure if he was with us today he'll definitely say that so what is the problem we're trying to solve what is the synchronization state and why is it such a hard problem uh let's start with this the simple example uh we have a page of the store for example and this one uh shows you the list of the progress that we have in the cart so this is like a cart page and we have also the cart icon component that also needs to show how many items do we have in the car for example in this case four items so let's take a look at this example and let's take a look at this component so this component needs the carb items count and it should be a simple number how can we provide that data for this component well there's multiple ways one of them could be through the input but then we'll still also need to have a container component above it that talks to the services and basically pushes the data into this component great that's one of the examples the other example we can just go straight directly to the service and ask what card products do we have you know and then we'll use uh some map operator and have some function that would transform the items of the card that we have to a single number right that the one that will be displayed by this component so we can inject the service and get the car products and get our number out of it great and let's see how this service would look like so here this car get our product would basically be a thin wrapper around http client which will do the api call and give us the observable of those card items which would be later reduced to a single number great that that kind of works right but uh let's take a look at the structure of this page a little bit closer so we have this car icon right component which is part of the header and that header is there okay so then we have a bunch of products that also need this information by the way so we have multiple car products components uh which basically they live in the car page so this is our structure which in itself lives in the car in the router outlet and in the end all of it lives in the app so this is basically how our components are nested uh let's break it apart and then take a look at the um at it as the component tree okay so this is what we have so how can we provide this data the card icons data to the card icon to this and also to the card page which also is interested in this card items well and one of the ways we can do it is basically provide the data to the app somewhere and then that would be passed down through the inputs to our you know components that actually need that data but you can see that that approach doesn't really work and it's really not the best approach because uh now your header is just passing this data no use of it it's like called property drilling so we definitely don't want to do that so what can we do instead well we can basically provide that data inject into both those components and provide that data the card item data that it needs but at the same time if they'll be all both of them will be using the you know the get card product method which is stateless at that point they'll each request uh this data multiple times but use it only once right so we don't want to have those extra requests so how can we do this well one of the ways we can do it is instead of you know exposing this get car products method directly to the components we can call it within the cart service and basically share replay that means that the result of it would be shared amongst all of the components that need that data and we'll expose some property called card items dollar sign which would be a reactive observable property providing that data and then in our component instead of calling it directly right instead of calling the gecko products we can just remove that and instead use that reactive property great so we have that solution however that means that the data we get once the scar product details and and that's it right so that data becomes stale this data in fact could be changed by multiple ways within our application for example you know we can remove items from the car we can purchase it we can remove everything or we can even add new items to this uh to the cart so our current items data is not static it's dynamic it's changing and we can there's multiple ways we can change it throughout the application so how can we approach that well before so we need to rethink how we expose discard items observable property instead what we can do is service with the behavior subject approach so that means that our cart service is a stateful service that has the subject in it and then it's exposed as an observable through the card items just like we had before now every time we call you know we need to add the product for example we can call the method but however we change this method they no longer return observables for us they'll return void so they don't do return anything but inside they do the call and they subscribe to that network request and then the result of that array that you know the newly added array they'll just push it to this subject inside so this is known as the service with the behavior subject approach it works really well because now our component still doesn't care what's changing the data who's adding products who's removing products all it needs to know that this car items you know is changing over time it's observable and that observable is reduced to account and basically displayed in our cart icon like this however one thing we need to do now as well is we need to let the service know that we're interested in this information so get guard products we have to call it it also returns void but it no longer returns us observable back right it basically triggers something inside so we can use it as an event to do something all right so this is a great approach and it works well for a lot of situations however there is slightly slight problem with this and this is why you know component component store solves the problem well for example uh the user really wants to add you know many products to the car and the click clicks user click click many times this ad so we have the first click that goes in right and then user clicks it again will trigger the next one so we have a second add to cart click however in the network request it could happen that the second request arrives before the first one the result of it this is what we call race conditions and you know right now with our services structured right now there's no way we can handle any of those because every time we you know click add it triggers a new request so this is where effects really shine because they can allow us to control the race conditions be it in the global and direct store or in the component store we have effects to handle things like that specifically all right so the race conditions is what the effects are for and this is what we'll be you know using in our component store let's look at how our component store is constructed let's take a look at its apis so this is a component store and like it's a completely standalone library that has nothing to do with the global and direct store it's basically you know one file uh with 300 lines of code and maybe less soon and it has a few operators so it has exposes us a few apis for example it should read write and handle side effects just like any state management library so for it we have select for right we have an updater and for the side effect we have an effect method let's take a look at them closer so so instead of our um cart service we will create a cart store which would be responsible responsible for this state uh of the card items the first thing we'll do with the clear interface that will be saying that which state it holds for example in this case it's car items it will hold that state then we'll extend the component store and we'll pass this interface as a generic into the component store so now we are tying this card store with the component store then we'll inject any dependencies that it has for example in this case it'll inject the card service however the card service we're reverting back to fully stateless meaning it doesn't have any behavior subject in it and every call to add a product to get the products uh would actually do the api call and return us observable back so we're basically regarding that back okay then we need to initialize the state the cart items you know empty arrays how we would initialize this one so how would we read stuff from our component store well we can use it with the select method select method will take the state and you explain it will take the callback that takes state as an argument this is what we have right now in our component store as a state and we'll show how exactly we're extracting the data for example in this case it's fairly straightforward we'll just do state dot card items meaning we're reading from that property and that would be an observable of our card items now we can listen to it where we want however we can do even further we can select that observable pass an observable into and pass that function that transforms um that those card items to the count and then we have another property the cart items count observable what's cool about select it it's it's highly highly performant it has all the share replays you need it has distinct little change and moreover it also unsubscribes when the component store is destroyed so it's all built into that select highly highly performed all right so this is how we read how would we write how would we update our state well oh yeah by the way this is our read so now in our cart i can component we can just listen for this card item count we'll just inject the store just like as any other service would do and just read that properly great like this also how would we write now well for that we have an updater updater is a function that describes how we change the values in our store in our component store and it takes uh two arguments first is the is the previous state and the new value that's coming into it for example card items in this case and then it will return an immutable version of new state so how we'll spread the previous one i will put those new card items there as a property so said cart items it looks like a property but it's a callable property now we can call that property and pass the new values into it for example as you see they're highlighted at the bottom the set card items can take these card items now and will update in state internally so the updater takes the how how we transform the state how we update it and finally the side effect in this case it's add product it's also callable property which will pass values into so every time we want to add a product we'll call the add product and pass a single value into it for example single string single id but what effect does it again orchestrates how we would work with those incoming stream of ids so that's why we work with observable so notice the ids is an observable how we would work with those incoming ids then we will pipe it and there we have a chance to uh display determine how exactly we want to control this race condition for example in this case is a concat map meaning that for any new ids that come in we'll have to wait until the new the previous one is completely finished and then will the new id will start looking at that one uh what do you mean what i mean by completely finished so for example here once i have an id then i'll call the card service to it to add a product so this is actually the one that would do the network request bring this observable back and when it's successful then i do something without value so in this case for example on a success i will push this new array of card items into our state with the updater if there is an error i'll change some status this could also be an updater so update status and finally i'll catch the error and map it to the empty observable meaning so i don't want to collapse my um my observable chain uh moreover in the version 11 that's coming out uh in somewhere late this november early december uh for ngrx we introduced the new operator called tap response that's basically does the same thing that you just saw which is a little bit shorter hey no boilerplate now all right so this is our side effect let's see how we can use our side effect so now in our component we'll inject this heart store and every time we need to add a product what we'll do is just car store dot add product and product id notice our product id is a single string right and what we have instead there it was an observable of ids and this is the beauty of side effects or effects is that we can really really control how we handle this so every time we push the new product into this into the store we have a way to handle those race conditions so here is a concat map but we could have chosen say merge map or exhaust map or switch map switch map would do for example if we're working on adding another product already right and the new id arrives we'll stop adding the previous one ln and start working with the new one exhaust map would do the reverse meaning if it's if it's working already on something it's adding something already you'll ignore any new values until it's busy adding it so if you have a second wallace like ignore it and merge map you will start them in parallel this is what our service with the behavior subject does by default but here we can really control it be very specific and all this complexity is no longer incidental we're very specific how we want to handle those race conditions right so now we finally have that control and moreover i'm very explicitly asking to handle the errors here because this is one of the biggest sources of error of broken applications as well all right so now we have that instead of calling it every time we'll do what we'll do instead we'll have a card store that's provided in root in this case and then we'll inject it and each would be listening for these uh selectors that it needs so how does it compare before and after well you see it here it's not you know inflating our code too much however when we get instead with the component store versus the service with the behavior subject what we instead get is a highly performant uh highly specific uh store that really handles all those risk conditions errors and everything else in between awesome so that's one of the use cases right a provider root so where else would we use the component store well provider root was one of them and uh while i show you here how to do it this is not the one that i would recommend to too highly uh because it basically brings that back multiple services that are provided which could cause this spaghetti of services or what i call uh service soup um what i do recommend is the other three approach instead and there is actually a good good value sometimes to do the provided unroot uh if the component store is responsible for a very highly narrow scope of state for example like permissions right permissions are used all over in the application uh and but it's very you know small state but used widely so that one's okay to be provided that's why i have this exclamation mark but then i have other examples that where capone still really shines for example a complex independent component state let's look at let's take a look at that one for example all right so it's very similar to what we had before however our cart store is no longer provided in root but instead it provided for a specific component and that component basically is the first node that owns the state and that you know goes into the children so it's the one that owns it and when i say provide it's a it's a components called provider so what does it mean we use it providers in the component metadata so we specify it there now our store would also be attached to the life cycle of that component a in this case that means that when that component is destroyed when the user navigates somewhere else the entire card store is unsubscribed and cleaned up completely so you don't have to worry about that at all awesome okay so this is complex independent store the next one is reactive local ui state this is very interesting one so for example here in the component b uh we have some state and we ship that state we wanted to have it somewhere else so we you know our component becomes very lean and very effective so we just put the business logic into the store beside it this local store there is not used anywhere else in the application only in the component b but this is where our business logic now lives it's provided for for that component it's injected in there and it basically handles all our business logic now so we can really easily test component b unit tested with mocking it all and just making sure our template works and all that stuff and then we can really test the business logic separately through the component store so this is the second example and final example is the shared common component state so this is a state where we have a common component that is used in multiple places in the application maybe even in the same place for example here we have a component that has apparent component of a and it interacts with it through input or output right so this component can be reused anywhere in the application but this component could be talking to the network request and stuff like that if he wants to uh so this is like a sub tree there and now we have a second common component that basically could live beside it on the same page right and each of them need to handle self-contained state when we had you know global and direct store for example we were trying to push it way upstream and maybe index by uh by something and then you know you can use the queries to get each component independently however right now with the completely with the component store each common component can just provide the common store and because they're they are uh component scoped providers like common store it would be unique for each and is uh for each common component so for example here each common component would have its own common store completely independent and then if you inject it somewhere deeper in the first or second level children's they will have access to their prop appropriate uh common store so this was the example that actually started the whole component store within firebase and this and later on this is how it got into the interx all right so i'll do the quick summary synchronizing state is a complex task and you have to be very specific how you do it state management libraries help remove those incidental complexities and make it very specific how do you want to handle your race conditions i ask you to handle all the errors as well so you remove a lot of complexity and you know you you think of state as a you know first-class citizen and obviously in direct to the rescue and now we have global store now we have component store that you can choose one or the other or both actually like firebase console does they solve different problems and they are really shine in the in the use cases where the solve so thank you for all of these fantastic people uh kevin yelco who originally created the component store within firebase brandon and tim who i reviewed a lot of my code and had a lot of back and forth on the design and everything and nicholas jameson from rxjs team uh who actually created one of the operators custom operators that we use within the component store and lars as well for hours and hours and hours of basically going over some of the details of it and providing a ton of feedback as well there so you can find uh all the info about the component store at our and drugs the ios site and don't forget to join our conversations at discord uh discord gg angular and we have a direct one as well lots of community uh discussions there definitely join them thank you so much bye
Info
Channel: ng-conf
Views: 2,924
Rating: 5 out of 5
Keywords: angular, angularjs, javascript, ngconf, ng-conf, programming, angular conference, ng conference, angular javascript, angular tutorial, Javascript Tutorial, Programming Tutorial, Computer Programming, Google Angular, Google Programming
Id: O_CkyqVMFH4
Channel Id: undefined
Length: 24min 46sec (1486 seconds)
Published: Thu Jul 15 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.