Reactive State Management Using NgRx and Angular
with Brandon Roberts
How do you handle local and global state in Angular projects? Brandon Roberts will show us how using NGRX
Resources & Links
Captions provided by White Coat Captioning (https://whitecoatcaptioning.com/). Communication Access Realtime Translation (CART) is provided in order to facilitate communication accessibility and may not be a totally verbatim record of the proceedings.
Jason: Hello, everyone, and welcome to another episode of Learn With Jason. Today on the show we have Brandon Roberts. Brandon, thank you so much for spending time with us today. How you doing?
Brandon: Hey, thanks for having me. I'm good. How are you?
Jason: I'm doing great. We just figured out that I broke the Learn With Jason site recently. So I'm watching the fix push to production right now.
Brandon: That's always interesting.
Jason: It's going great for me. Wait a minute. I fixed you. Oh, boy. Okay. Well, it's still broken. We'll deal with that. So, what I'm going to do is I'll post everybody to some links here. I broke the live captioning on the home page today, everybody. We'll have to go directly to the actual scene of the live captioning if we need it today. So let me find that. While I'm looking that up, Brandon, for folks who aren't familiar with your work, do you want to give us a background on yourself?
Brandon: Yeah, sure. I'll give my elevator pitch. My name is Brandon Roberts. You can follow me on Twitter @BrandonTRoberts. I block people sometimes. I open source. I maintain a couple of open-source projects, one of which we'll talk about today. And I'm also a developer advocate on Narwhal, which is open source. So, that's one of my -- another project that I work on. But, yeah. Glad to be here.
Jason: Nice. Yeah, yeah. Now I'm afraid I'm going to get blocked because I don't know what the words are. (Laughter)
Brandon: Oh, I'm sure the chat will let you know.
Jason: (Laughter) Great, great. This is going to go good for me. I'm going to get blocked midstream. But yeah, so I'm super excited to talk today because we're talking about something that I've known about for years and have been completely intimidated by for years, which is reactive state management. When we get into reactive state management, it starts to feel magical to me, and typically my response to magic is to run away, try to kill it with fire. I respond like a 14th century villager to anything that feels like magic. So maybe you can help demystify today. I think that's what I'm excited the most for. We're going to peek behind the curtain, see how the magic is made. So can you talk a little bit about, you know, what is reactive state management? What happens in that style of app?
Brandon: Right. So reactive state management -- and I'll talk about this mostly in the context of Angular, of course, but Angular is built on top of -- or Angular uses RxJS, which is a reactive library that uses observables as its primary handling events. But being reactive in that frame of mind is more declarative than -- using a more declarative approach. So we deal a lot with functions and a lot of functional programming is used in RxJS. This is where some of the things come in, like operators, but the main thing is the observable itself, which gives you an easy way, like a primitive to listen to some events, whether they be synchronous or asynchronous, handle listening for those events that happen over time, and giving you a way to tear down listening to those events. So, the reactive part of it itself in Angular is to take that model and use that to manage the state of the application over time. Like, if we were just talking about calling functions, then there are easy ways to get into grace conditions using those kind of -- in some cases you can use conditions that way, but using the reactive way is more of a declarative, where you are kind of mapping out what you want things to do, based on some event or some action that happened. That is what we kind of strive to in reactive applications and Angular. You want everything to be mostly a stream that happens over time as opposed to -- yeah.
Jason: So, to repeat this back to make sure that I have the mental model right, when you're talking about observables, which are kind of at the bottom of this model, what you're doing is you're basically taking a piece of memory, an object or something and you're saying, I want you to look at this, and I'm going to send events to it. So this is sort of like having a click handler. We're doing like an add event listener, and when a click happens, we can perform some kind of function. This is similar in concept, but what we're saying is I have some data, and when a change happens, I want things to be subscribe to that change so I can update my UI based on what changed in the data.
Brandon: Yep, that's exactly it. Pretty close comparison to an event handler. You get -- like I said, you can add an event listener, tell it to listen for a certain amount of time, and then say remove that event listener. So the concepts definitely line up there.
Jason: Gotcha, okay. So that immediately makes it feel less intimidating. I know how to do a click handler. I can write, you know, different events on the dom. So let's just apply that knowledge to data. So, when you're talking about this, you said, you know, that in Angular, this is a popular approach. I think I've seen NgRx all over the place. Why do you think this approach holds water? Like, what's the -- what do you think is the real benefit here, and why is it such a strong way to manage your data versus other approaches you've seen?
Brandon: Yeah, for me, some of the things RxJS provides in general, the setup, listen, and teardown is one thing. If we're just kind of talking about it in general, if we're comparing it to something like a promise, promises are eager versus observables that are more lazy. That would be one thing. They're also -- observables have the ability to be canceled. Once you start up a promise, you essentially have to wait until that promise emits a value. Observables are events or data or things that happen over time as opposed to a promise, which is more of a one-shot deal there that you kind of listen to and observe as it happens.
Brandon: Like I said, just thinking in general about it, those are some things that we can point to, to make a comparison per se. We're not saying that one is -- promises are definitely less to comprehend, I would say, in general. Just things baked into the web platform. But it does give you some differences and advantages that are important if you're working inside that mind frame. It is a bit of a shift there, I would say.
Jason: I get you. I understand. So, this is kind of an interesting idea. What we're basically saying is instead of having to think through all the different states of our data and being ultra explicit about exactly what's possible and writing all of that logic, we can instead say we want to be -- okay, now I understand. We can be reactive to our data. When data changes, we can just say, ah, I've observed a change, I'm going to respond to that change.
Jason: You know, that also feels like -- I don't know. It's a very human way to think about data instead of a computer-y way where it's like I've built the pipeline, and the data must flow inside of this pipeline exactly as I've defined. We're instead saying here's a stream of data, if you want to listen for different events, you can pick those up. And anybody who uses those -- so when data gets picked up from a stream with these events, is that something where sequence matters? Or is it like if I have 100 pieces of UI that are all listening to the same event, the order that they all grab data, even if they modify it a little bit, are they kind of dependent on each other? Or is this independent, like the event goes out, they update based on that, and if it changes the data, it has to emit a new event?
Brandon: Yeah, so -- like I said, we'll get more into this with NgRx, but all the listeners of that stream of data or property would get that event at the same time or as it goes through and emits that event to all the listeners. Depending on how you're structuring that data, there wouldn't be somebody necessarily modifying that. A subscriber wouldn't be modifying that data along the way, depending on how you have it set up. So each one of them would operate independently on it. Now, you can share those streams amongst many listeners, but in the context of global state or a local state, if we're listening to those streams and each one of them kind of operates independently. Like I said, you can subscribe to that and do different things based on those different events. So there is some safety there in that it's not going to be unpredictable to what you get. And this kind of gets into operators in RxJS, how you modify that kind of pipeline or sequence that happens. But yeah, that's something that in itself the observable and the listeners of it all get that piece of data.
Jason: Nice, nice. And do you find that -- like, is there a certain type of app that's better suited to this? Or do you find this being now that you've got this mental model, this is just the way you think about data?
Brandon: Yeah, it is more the way I think about data now just because I'm more comfortable with it. Like I said, if you're used to using -- or kind of building your own streams that simulate that, then it's a little different. It definitely helps to think in that mind frame. I'm going to quote the founder of NgRx who has said this many times. Everything is a stream. If you start thinking in that mind frame, then it makes working with RxJS and Angular easier if you embrace that way.
Jason: Nice, okay. I get it. I mean, I feel the same way. When I started thinking about -- there's a lot of ways I write code now, where that's just the way -- that mental model I have now. It makes things make sense. So, all right. I'm really excited. I think at this point, probably what makes the most sense is to actually start doing a little bit of code. We can see whether or not we can get me thinking in stream. So, let's jump over to pair programming mode here. This is the deploy that hopefully fixes the homepage. Let's see if it works. Nope, still got some errors.
Brandon: You're doing that Friday deploy thing on a Tuesday.
Jason: Literally fixing it live. Okay. So here's what we've got. This is the iframe I embed on the homepage. If I get rid of the extra font stuff here that doesn't copy/paste very well, I can send this to everybody. This is the live captioning for the show. That is made possible -- we've got Rachel with us today from White Coat Captioning. Thank you so much, Rachel. That's made possible through the generous support of our sponsors, Netlify, Fauna, Hasura, and Auth0, all of whom are kicking in to make this show more accessible to more people, which I appreciate quite a bit. So head over to this streamtext.net player if you want to follow along with the captions today, and I'll figure out what happened on the homepage before our next stream so this'll actually be fixed. Also, while you're clicking on things on the internet, make sure you go and follow Brandon on Twitter. Like he said, he's a good follow. Lots of good information on there. You know, you can try to figure out how to get blocked. (Laughter)
Brandon: Yeah, if they follow my Twitter long enough, if they haven't already, they'll quickly find out what the keyword is. It's a fun game to play, though.
Jason: Great. I hope that I win that game instead of losing it. And today we're also going to be talking about NgRx. This is the core of our focus today. I'm going to shut all that down so we have tab space. Okay. So here's I'm ready to create a new project. If I want to get started and learn this, what should my first step be?
Brandon: Yeah, so first we'll need to create a new Angular project. I use NX, that's my go to. So if we go to nx.dev, we should be able to go to the site there. Copy that command, that npx. So start there. If you enter that, it will give us some name we want to have for this show or for this project, anyway.
Jason: Okay. I'm going to call this reactive state NgRx. Look at these options.
Brandon: Yeah, it has a lot of presets. We'll go with the Angular and start there.
Jason: Application name. Probably just name it the same thing.
Brandon: You can just name it my app or whatever your preference is.
Jason: Is this one like a slug? Or is this like plain text?
Brandon: It's just plain text. It'll be the component -- sorry. It'll be the selector for the component. So it could be like my app or just one word. There won't be multiple words.
Jason: Oh, I got it, I got it. So, let's call this -- like that kind of thing?
Brandon: Yeah, sure.
Jason: Spell it right, though. Okay. Here we go. Do you have any preference?
Brandon: CSS is fine.
Jason: Okay. Nx cloud?
Brandon: You can just click no on that one. We'll go through and create the workspace there. It usually is pretty quick about installing that, but if it takes a little bit, we can kind of talk about NgRx more in general and kind of like the background of it as a project and how it's evolved over time as a state management library. Or it's a set of libraries at this point.
Brandon: But yeah, that is where we'll be.
Jason: All right. Maybe this is worth doing a quick shout as well to Nx. We have done a little bit of work with Nx in the past. We had Adam Barrett on the show. If you're interested in how Nx works and what we just did using that, this would be a good episode to watch. But the short version -- maybe you've got an elevator pitch. What's the short version? What is Nx?
Brandon: So Nx is an open-source build framework that does a few main things. It maps out the dependencies of your projects so it knows how your projects connect together. It has support for generating projects for many ecosystems. We support Angular, React, and Node, which are the main ones. The other one is local caching. If you are building a project, we say you never build the same thing twice. So if you build a project and you didn't run that same build again, then it will immediately give you the results for that.
Jason: Oh, nice.
Brandon: Yeah, you can kind of share that across your team or your organization, so it kind of scales up from there. But it's a built framework for mono repos and helps you build applications at scale. That's the elevator pitch for Nx, anyway.
Jason: Cool. Okay. So, I really like it. What I like about it is you've got a really nice flow in Nx for if I'm working on something that would be an npm package but doesn't make sense to publish as an npm package. Nx has such a good flow for making it feel like I'm using an npm package but not forcing me to publish it. That's such a great little bonus of using it.
Jason: So, now that I've got this, it looks like I don't have Nx installed globally. Am I going to need that?
Brandon: You can just use npx if we need to run any commands or anything. But yeah, that should be good.
Jason: Okay. I just realized I put this in the wrong place. I'm going to move this whole folder up a level. Then we're going to move into it. Okay. So let's open this thing up and take a look at what's inside. Inside of our project we have -- so, this is already a little different from most projects I've seen because I don't see like a source folder, right. There's apps, libs, tools. So what are we looking at here?
Brandon: Yeah, the apps is mainly where we'll be working out of today. Usually if you're having one project, it'll just be under a source folder. If we had multiple ones, then they would be -- you could have multiple apps. Like you said, talking about the internal npm packages or packages within that workspace. You would have libraries there. But the app is where we'll focus on today. We'll just kind of create everything inside that reactive state application there. Then we have the source folder, which is a little more familiar.
Jason: So this is like the Nx mono repo setup, where apps is one thing, then if I have a library that I wanted to share but that's not necessarily a package. I'm not 100% sure what tools are. So this is like the organization. Then down in here, this is like -- in a normal project that you just kind of spun up from scratch, what you would end up doing is this would be your whole folder. So this end-to-end testing that's out here, the libraries, the tools, and then all the Nx config that'll let us -- gee, it comes with tests already running and all sorts. That's great.
Brandon: Yeah, you definitely get a lot of the -- you get it out of the box. When you're doing Angular or React or whatever your project is, those would come preconfigured for you.
Jason: Nice. Okay. And it looks like I'm missing a few things I need. I probably need Angular thing. Do I need this Nx console, do you think?
Brandon: You can, but we don't need it for this. It's just a GUI on top.
Jason: If we don't need it, I won't install it. Angular language service seems like it'll be useful. Probably need to see what's going on there.
Brandon: Definitely gives you some good helpers for your templates and things.
Jason: So default. Let's see how much Angular I remember. Not something I have a ton of experience in. So, what we've done here is we're pulled in app module from app.module here. This is the root of our code, which pulls in app component. That pulls in the template and CSS.
Brandon: Yeah, at its core, you have modules there. This is just a template that we'll just replace. Yeah, there's the top-level app module, which Angular itself has, what we call ng modules, a way to wire up your components and make the application aware or parts of your application aware of components and providers. So this is just a top-level one that boot straps the application and gets your browser level dependencies set up there. The app component is just the main component that'll start up when we start serving the app. So, like I said, it has the ng modules and components there, the main things you have in an Angular application itself.
Jason: Gotcha. I'm also seeing my overlay tweaked out here. So let's refresh that page. Now people can see things. All right. So, good. Then inside here in this HTML, this is basically where we'll kind of start.
Brandon: Yeah, we can clear out -- well, we'll need to add the NgRx packages first. If you install @NgRx. And I don't know what your preference is, if you like to do the multiple packages at once or one at a time.
Jason: Oh, we can do multiple packages at once. That's all right.
Brandon: Cool. So you'll start out with store. That's the main Redux package. We'll do @NgRx/store-devtools. The last one will be @NgRx/effects.
Brandon: And that's it. That'll be what we need.
Jason: Got it. Here we go. So you said the store -- you said Redux. Is this using Redux under the hood?
Brandon: It's not using Redux under the hood. The original project was inspired by Redux, but there's no concrete relationship between the two. I call them cousins because they're like close enough in functionality, or like the idea of both projects is close enough but not exactly like siblings.
Jason: Gotcha, okay. I understand. So now we've got the NgRx store, dev tools, and effects. I'm going to ask you questions about what each of those does as we pull them in here. If I want to run this project so we can see where we're starting and mess around, do I run like npm nx?
Brandon: Yeah, npm nx serve would be the command there.
Jason: Okay. Oh, wait, I messed something up. Npx nx.
Brandon: Yeah. I saw three people in the chat. Saw Will Johnson, shout out to him. AJ, shout out to him.
Jason: Yeah, I saw Ben came in with a raid. Thank you so much for the raid, Ben. Saw Mike Hardington in earlier. Lots of great folks. Thank you for hanging out with us.
Brandon: Yeah, so we have the app running now. We can just go to local host 4200 and open that link up there to see the app that it starts up with.
Jason: Okay. So here's a nice -- oh, this is a good starter, too, because it's got, oh, you confused about how this works? Here's a million ways to learn more information. This is great.
Brandon: Yeah, we'll end up just nuking all that.
Jason: So for now, I'm going to just -- boom, gone. We have an empty file now. I can put in, like, hello, chat. Come out here. Does this hot reload? Oh, it hot reloads. That's beautiful. So, we have a nice development environment. We've got things running. Now I'm ready. I want to make something reactive. So what should I do first?
Brandon: So what we're going to do here is start out with a pretty straightforward example. We're going to use a counter and use the store to update the state of that counter or the value. So we'll keep it straightforward. Just create a button, and we'll do the -- we can do the counter value there. Just create a div with the counter value. We'll create three buttons. One for incrementing, one for deincrementing, and one for setting the value.
Jason: Our value will be here. Or let's make it -- let's keep it in the spirit of the show. We'll set it up as a count of boops. Then we'll have a span we can put that if we need to put it in an element. Then we'll have a button to add a boop and remove a boop. Unboop. I don't know if you're technically allowed under the laws of physics to unboop. We're going to say today that you can. And what was the third one?
Brandon: Reset. But we can just do the add and unboop, if that'll work.
Jason: Yeah, let's start here. If we need to add more, we can. So this now gives us add boop, unboop. We have buttons. These don't do anything. We just made them into the markup. But at this point, I think we're actually ready to start doing stuff.
Brandon: Yep, and there go the boops. So if we go into the app component.ts, we're going to define -- yeah, that one. You can remove the title there. We'll set value in this class, and sent it to zero. So if we go back into the app component and replace the zero with two sets of curly braces -- yeah, there we go. That'll be enough to start with there.
Jason: Then if I change this to show it's hooked up, good. Perfect.
Brandon: Good starting point. So we want to add boops and unboop boops. If that's the way we're going to write it.
Jason: (Laughter) Right.
Brandon: Okay. So what we want to do to manage this with NgRx is we want to kind of set up the Redux flow here. So we're going to create a file called -- we'll call them boop.actions.ts.
Jason: Where should this live?
Brandon: This can just live in the app folder. Yeah, just create it right there
Jason: Like that?
Brandon: Yep. So what we want to do is define the events that we want to trigger here for the boops. We have a function in NgRx called create actions. So if you import -- or we can create -- yeah, import create action from @ngrx/store. This is, like I said, a utility function, where we define the type -- or the event and the source of where this came from. If we want to create a constant for -- or we can export that. If we export that constant, we could say boop added.
Brandon: Then we'll set that to create action.
Jason: Create action.
Brandon: Then inside there, we'll define a text string. This is just a descriptor of what we're doing here. You can use boop added. We usually break this up into an event -- or a source and an event.
Jason: So what's the format that you would use for this? You would do square brackets.
Brandon: Yeah, square brackets and call it boop. We could put boop in there. Then just put added out on the side of it.
Jason: Like this? Or like with no space?
Jason: We can probably do like removed so it's a little more -- like a little easier to keep on a convention here. So we've got one for added, one for removed. Okay.
Brandon: So we have our actions in place there. Next we want to create the reducer that's going to manage the state of that value. So next we'll create a different file called boop.reducer.ts. You can just inline that there. So we have create action. Then here's where we'll create the reducer that'll just manage the count of boops that we have. And to create that, we have similar functions for create reducer. If you import create reducer from NgRx store -- yep. So we'll have that one. And for the -- well, for the export, we'll export a constant for initial state and just set it to zero. That'll kind of mimic the initial value for what we have there.
Brandon: So like I said, if we were building a more complex about and you could define an interface, but here we're going to keep it straightforward here. So, next we want to create the reducer that's going to manage that value. We're going to export another constant for reducer. You can set that equal to create reducer and open that up. The first argument there would be the initial state we just defined. Then we'll -- the next set of arguments would be the handlers. So each handler uses an on function that's also imported from NgRx store. Either you can import it first or type it out.
Jason: Let's type it out. Hopefully it'll auto complete for me. It looks like a spread. Is that an array?
Brandon: Yeah, each one of the arguments after that will be the handler. So they'll be separate.
Jason: Oh, oh, I understand. Okay.
Brandon: So if you just type on there, it'll give you that import from NgRx store. And we could put that on a separate line there to just make it a little easier to read. Okay. So inside of the on handler, we want to listen for these actions, the two actions that we define. Or we'll start with the boop added action. So if we want to import that one. The first argument or the first set of arguments is the actions that we want to listen to. So we have boop added and boop removed are the two. We only want to -- on the boop add, we want to increment. Then we'll have one for boop removed.
Jason: Okay. So we've got these in.
Brandon: On the boop added, we'll put a comma there. The actual handler for that action will be what we're defining next. So each -- and this'll just be a callback function that we have there. It'll be state. And if the option itself has some additional data associated with it or a payload, then we could manage that also there.
Jason: Yeah, so it would also give us the action itself.
Brandon: Yeah, it'll also give you the action itself. If we added props to the action, we could use those two, but we're just dealing with the state right now. So we'll just open that up and return.
Jason: So I could do state plus one.
Brandon: Yep, exactly. Then on the boop removed -- so that's all that we have to do for the handler there. Then the boop removed would be state minus one.
Jason: Great. Now we have an initial state, reducer, actions we can send and remove. This feels like -- okay. This is all making sense to me. Whenever a boop gets added, we want to add one to the state. Whenever one gets removed, we want to remove one from the state. This one we'd probably want -- you can't go to negative boops. So we would go --
Brandon: Yeah, you can just define if you want -- how you want the logic to go in there.
Jason: Less than or equal to zero. We can return zero, otherwise we can return state minus one. That's horrible code. But let's all pretend that was clean.
Brandon: Look away, everyone. Look away. Okay. Now look back. So now we have the two main things. We want to wire this up to the store. We'll register the boop counter in the store. To do that, we'll go to the app module.ts.
Jason: That's here.
Brandon: And this is where we set up global services or global providers. I think react has something similar. Or providers are used that way in react.
Jason: So this is where we're effectively -- at this level, we are wrapping app component with whatever we need.
Brandon: Yeah, we're providing whatever the services are needed for the entire application.
Jason: So our store is going to go in this providers array?
Brandon: We'll add it to the imports.
Jason: Oh, okay.
Brandon: It does have providers with it, but by convention, we'll use it -- we'll register it using the store module. If we go into the -- yeah, after that one, just add a comma there. And just store module -- yeah, if you import the store module, with a capital S, from NgRx store.
Jason: Okay. So we'll put in store module.
Brandon: Then we'll add for root. We'll add that with an empty object just to start out. One more thing we'll add is the store dev tools. If you import that from the import store dev tools module --
Jason: That comes in from store dev tools.
Brandon: Yeah, it would be store dev tools module there.
Jason: Store dev tools module. Got it, got it.
Brandon: Yeah, we're keeping the Ng module convention there. So it'll be store dev tools module.instrument. If you have the Redux dev tools installed, you could use the same tools as if you were in a React project. Anything that can hook into the store dev tools there, you'll be able to see.
Jason: Do I have that? I might have that. I'm not going to try to install something else today. But yeah, this is great. Oh, I do have it.
Brandon: There you go. Okay. Cool.
Jason: I'm going to make this bigger.
Brandon: Pops right up there.
Jason: Slick. Here's our store popping right uh. Action, state. Oh, we made it empty. That's right.
Brandon: We haven't registered it yet. If we go back to the store module for root, inside that object we can give the key of what we want this feature to be. We can call that boops, if you want to call it that. Then just import the reducer from the file.
Jason: Reducer from boop reducer.
Brandon: If we were using Redux, you would use configure store. Then you would have your initial value for that particular slice or piece of state. So it's just -- at the end of the day, the store is one big object. We're using the dev tools to just inspect that object there. But yeah, that extension has been out there for a long time. We definitely make good use of that.
Jason: Absolutely. Yeah, so it looks like you can use Redux dev tools in Chrome, Edge, and Firefox. I'm working in Edge, and it's working great. Now I can see we registered boops and added in our reducer. We've got it. So that's doing the thing we want. Does this mean -- so at this point, we've created actions. We created a reducer. And we've registered those as imports for our app component.
Brandon: Yeah, we registered the store as a global service. We're looking at the global state management side of NgRx here. Now we can actually just use that -- we can set up the store in the app component to handle the state of that counter. So first, we can see what the -- we can actually update the count in the store without having that actually reflect there first. So what we want to do first is to inject a store service, which is available globally because we registered it there. If you import store service from NgRx store -- a lot of stores. Well, it would just be the store. Sorry. I've referred to it as a store service, but it's just store. Then in the app component, we want to add it to the -- we want to define the constructor. Where we can inject a store service.
Jason: Okay. And the way you do this is?
Brandon: If you just type constructor. Then we'll open that up. Then we'll add the store as an injected dependency. So inside of the parameters of the constructor, we're going to set that to a private property. This is one thing Angular does. It does have dependency injection. If I want the store, I can just find a store as a property. So we can just call it store. Use the colon, and then use the store import that we imported from the top there. So it knows.
Jason: Oh, okay.
Brandon: Yeah, because we registered the store, now it gets injected automatically into the app component there.
Jason: So then -- I haven't written class-based stuff for a while. If I remember correctly, what we've done here is we're declaring count on the class. So in the constructor, we'll default it to zero. That's okay. But here what we want to do is get this store and set this.count to store.boops.
Brandon: It won't be that straightforward. We have to subscribe to the store to get values out of it.
Jason: Okay, right. Because it's an observable, yeah.
Brandon: The store itself is an observable of the state over time.
Brandon: So if we -- to update that -- well, yeah. We can subscribe to the -- we'll use one of the Angular hooks to subscribe to the counter. So outside of that constructor, if you define another method there in the class, it's ngOnInit. This is just a lifecycle hook that is called after the constructor is called.
Brandon: So here we want to subscribe to the store so we can at least see the value getting updated. So next we want to say that this -- we can subscribe to the store saying this.store. Then we'll say subscribe.
Jason: Is it this auto completed one here?
Brandon: No, it won't be that one. We'll use the select. So this.store.select. That'll take a callback function. Then we'll say the state is a type of any. Then we'll say state.boops. I believe that's what we named the value for the piece of state there.
Jason: Oh, it does not like whatever I just did. There we go.
Brandon: So, state.boops. Okay. There we go. Then after that, we actually have to subscribe to this observable. So they did an update on the value of the count. What we want to do next is do a .subscribe on the end of that. Then the value is -- this'll just be the value that comes out of the observable. We can just use value as a name there and use a call back. Open that up as a callback function.
Brandon: Then we can set the count on the class to that value.
Jason: Like that?
Brandon: Yep. Okay.
Jason: All right.
Brandon: So any time the store is updated with that counter, we'll update the local value here. Now, like I said, we could get into the weeds of observables and subscribe and unsubscribe, but for this one, we'll keep it straightforward. Next, we want to dispatch the action. We kind of want to create this reactive loop within our global store of dispatching an action. If we go back into the app component -- well, let's stay in here for a second. We can do it a little more straightforward if we do it here. So if we go into the -- if we define another method in here for add boop for the method there and then what we want to do is dispatch one of the actions that will update the state in the store. So we'll do this.store.dispatch. Then we can just pass the boop added event or action.
Jason: And this is the one we're going to import?
Brandon: Yep. And we'll just call that as a function.
Jason: Definitely don't need whatever that was.
Brandon: We don't want working threads. That's a rabbit hole we don't want to go down.
Jason: (Laughter) Okay.
Brandon: So we got boop added. Then you want to call that as a function.
Jason: Oh, outside. That's what we want.
Brandon: And we'll use one for boop remove -- or remove boop also. So we'll use the remove boop there and boop removed for the other event there. Okay.
Brandon: So we have those two actions there, the add and remove. That will update the counter on the page there. Essentially, that is what we need for the global state, to make this reactive, where we can see what the -- so we have it hooked up to the dev tools. Any time we dispatch an action, it's going to receive the -- the reducer is going to receive that and update the state. And we're subscribing to that value, so any time that value gets updated, it'll automatically update that count.
Jason: And I don't actually need this to be in a span. It's just going to work, right? So, now we can call these. So because we've declared these in the component --
Brandon: Yeah, we do need to add them to the template to use in a click handler. We're kind of connecting those two things together. So in the app component, Angular uses parentheses for setting up events. So we just use a -- click inside of there. Yep. Type click. What we want to do is call the -- so, we set up the event using the parentheses. Then we'll set that to equals. Then we use double quotes to say what we want to do when that event happens. Here we just want to say add boop. Then we're getting some Angular language services helping us out here.
Brandon: So we'll call that method the add boop when we click on that. Then we'll have the same one for remove boop. So we have to call those as methods.
Jason: They have to be called?
Brandon: Yep, they have to be called.
Brandon: That allows you to -- the click events, it gives you that information there with it. So if you have something you needed to do with that event, then that's where you could pass that event to the click handler. You already got it rolling there.
Jason: Yeah. And this is -- like, this is slick. Check this out. This is like a standard complaint that you'll hear about any type of Redux implementation. Oh, well, what about the boilerplate? Okay -- is that the word?
Brandon: Hold on one second. I got to go to Twitter.
Jason: Damn it! (Laughter)
Brandon: You fell right into the trap.
Brandon: But this is a standard complaint. People say I don't want to have to write all this. But looking at this, we didn't write that much. It's really clear what each piece is. And out the other side we get this really declarative way of getting at what's happening. I know when I click this button, I'm going to add a boop. When I click this button, I'm going to remove a boop. And when we do that, we have freaking time travel. We can go back and forth in our app and see where we were. That is incredibly powerful.
Brandon: Got all the same benefits. Usually some of the same complaints just with the pattern in general of boilerplate. But the time traveling does win some people over. So there's a give and take there.
Jason: This is one of those things. I think there's this sort of philosophical debate that happens. I saw Kat Marchand had a really good take on this. When you start looking at setups like TypeScript, when you start looking at setups like having these action dispatchers and these conventions, when you're first writing code, they feel like a hurdle. You want to get rid of those constraints. You're like, oh, I'm feeling hemmed in by these TypeScript types. I'm feeling hemmed in by having to write all this boilerplate to get this code out. I'm feeling like ham strung. I can't just get out there and create. But then when you go to maintain this or you go to hand this off to your teammates or -- you start to think, boy, I wish that this had more, like, structure to it, right? I wish it was easier to figure out.
Brandon: I feel like you're trying not to say the word here.
Jason: I'm trying, I'm trying. But I think the real power of this is when you get to the point that you are actually trying to maintain software, if you have to go refactor something, you got to change out some code, hand this off to a teammate and you're going to work on a different project, having types, having time travel, having this really clear structure makes it much easier to do that. Like, as a developer, I can drop in and open up my dev tools and see, ah, okay, when I click this button, it calls this action. I can search in the code and figure out where it is and find out how it works. So, that's the sort of thing that I think is -- you know, sure, if you're working just you, this is an experiment, you're just trying to get something, you know, work out an idea, maybe you don't need all of this. But if you're trying to build something that's going to live, this is going to help a lot.
Brandon: Yeah, and I think that a lot of -- I think the reason why it's been more popular in the React ecosystem and is popular in the Angular ecosystem -- and I say this a lot -- sometimes it's more about the process and the pattern is what people kind of gravitate towards. If you're working on a big team and you have maybe people onboarding and offboarding a lot and you want something that's predictable and that you can say, you know, we didn't create this here, this is something that's been battle tested outside of Angular and react and kind of flows back further than that, something you can use in that context to onboard team members with. Or if you have people that are more comfortable with RxJS, which this is all built on top of RxJS, so the streams and observables and everything are just baked in, it will create -- you know, it's a way to create these streams for you so you don't have to worry about am I creating the right one, am I using all the -- like the operators and things. But give you that flexibility without having to roll your own from scratch.
Jason: Yeah, and so I would love to see -- we have probably 25 minutes left here. I'd love to see how this expands out. So as we're working on more parts of the UI, maybe we can add something that listens to events. We can set a level. If it's under five boops, we'll say the boop level is low. If it's between 5 and 15, we can say it's average. Over 15, we'll say high or something like that.
Brandon: Yeah, sure.
Jason: So to do that, I would -- can we just create a whole new component?
Brandon: Yeah, sure.
Jason: So let's -- I'm going to need a lot of help here. (Laughter)
Brandon: I got you. So, I think the quickest way -- well, no, that would probably be -- we can go to the command line and just generate a component for the app that way. If we do npx nx generate, then say component. Then you can give the component a name.
Jason: Give it a name of -- let's call it boop level. Should it be like that?
Brandon: I think that works. We'll see if it normalizes.
Jason: Oh, cool. It like kabob cased it for me.
Brandon: Yeah, so we have the boop level counter there. Or boop level component, excuse me. Then if we look at the component, it's a similar setup there where we have the selector. It also gives you the test and thing there is. So if we look at the selector and component, this one just has boop level works in there. So if we go back to the component, you can copy that selector there, that reactive state, ngrx boop level. We can drop that into the app component. That will at least give us the component there.
Jason: Like that?
Brandon: It'll be open and close tags. It won't be self-closing.
Jason: Gotcha. Not self-closing. All right.
Brandon: So that will give us -- oh, then I got to restart it because I generated the component. So that would give us the component to share the value with this other component and do things based off of that.
Brandon: That still is all well and good. Even the logic worked there.
Jason: Hey, my really, really great code actually functioned appropriately.
Brandon: There you go.
Jason: So that all makes sense. We've generated a new component. And this is where I think, you know, you mentioned convention and patterns. Now that I've seen this once in the app, I know exactly what to expect when I look into another component, and I know where things are going to go and all of those things. So the one thing I'm unclear on is the app component. We injected -- hold on. In the module, we added the store and all this stuff. Now this is wrapped around our boop component. Does that mean there's inheritance? Or do we need to also bring in the store to the boop component?
Brandon: You do have to inject it into that component, but it is provided from the top level down. As you see, the boop-level component is in the declarations of this component also. So it automatically gets -- so yeah, the CLI did that for you as far as importing and adding it to the declarations. It has all the same access at the app component at this level.
Jason: Okay, okay. So does that mean here I can --
Brandon: Yep, so in there you can just use private again and store and just inject the store there.
Jason: Yep, okay.
Brandon: Now you have access to the store there. We can do some things in here with the count. You can just declare it directly in the class there. You can say this.level. For here, what you want to do -- I guess tell me what you want to do.
Jason: Yeah, my thinking, if I just kind of pseudo code this out. We've got our boops, and let's say it's ten. We would do like if boops is less than or equal to five, we'll say this.level is low, else if boops is greater than five -- actually, we could even just do greater and equal to 15 and say this.level is high. Then the last one would be else.
Brandon: Then we just want to show that in the component?
Jason: Yeah, I think we can just show the level. If we set the level, then I can go to my component. I screwed it up. We can say is level, right. So I've made a mistake in my code down here that's causing it to fail. Oh, I know why. It's because I have extra curlies. So this is kind of the idea here. We set it to ten. It auto completes to average. Then if I set it to 17, it'll say high. If I set it to four, it'll say low.
Brandon: Okay. So we'll do something similar. We'll subscribe to the value of the boops. Then we'll use an operator to map that value to the level. So we won't -- in this case, we'll do this in a more reactive way. We'll say this level -- we'll start out with this.store.select, and then we'll just use the state again here. Like I said, we're not having to go full type safety. We can wrap that state any in parentheses.
Jason: Oh, right, right.
Brandon: Then say state.boops. So that'll give us the value of the boops themselves. What we want to do is instead of saying, okay -- well, we're still going to use some of the if logic here. What we're going to do is add an operator. We're going to use a pipe in RxJS that will let us turn the current value of the boop into a string. So what we want to do next is on the next like line, you can use .pipe. This is a way that you can -- and this is kind of where the extra parts come in of RxJS to where you can use operators to perform basically function calls on the data that's coming through.
Brandon: So what we want to do is we want to do -- we want to map the value into a string. So we can bring in the map operator from RxJS. So if you put map inside of there. It should be all lower case. You may have to import it.
Jason: Lots of options up here. So that was in effects?
Brandon: Well, that will be from RxJS operator. So if we import map from RxJS/operators. And they've worked on this some more also. I'll throw the plug out there they're looking to consolidate this into one import. But the map itself is just a function. So what you want to do here is map. It's just a callback function. It'll be the current value of the boops. Then we can have a callback there. We can wrap that logic that you had into -- put that logic into the function itself.
Brandon: So instead of setting the level, what we want to do here is return a new level. So operators are just pure function. What they do underneath is take in the current observable and return a new one. Since we're just mapping to a new value, it'll just do that underneath for you. So now that we have that set up, what we want to do next is set the level to that store.
Jason: Like that?
Brandon: Yes. Then where we have --
Jason: What doesn't it like?
Brandon: Observable, low, high, average. Yeah, it's telling us that needs to be an observable. So you can take out that value there. We'll add a type to the level. So just put a colon. We want to say that this is an observable of a string. So if we put observable there and it takes a type, then you can take out --
Jason: Is that right?
Brandon: The value itself is just a string. You can take out the low, the initial value. Yep.
Brandon: Just take that out. It's probably complaining now that we have an uninitialized value. So if we put an exclamation at the end of level, then that should make it happy.
Brandon: So what we're saying is that level is an observable of a string. So this is going to be the value of that over time. It's correctly telling us in the template that we haven't subscribed to this observable yet.
Jason: Got it.
Brandon: So the thing in Angular that wires this up for us is the async pipe. What the async pipe does is -- like we mentioned before, we talked about subscribing to values or like executing a promise. What the async pipe in Angular does is it kind of creates this psych until our template, which is a little different than what we did in the other component. We actually did a .subscribe, and we listened to that value as it was being updated. So, what we want to do is go to the template, and the async pipe itself will do that for us. So, in the boop level component here, we want to add use the async pipe. If we do a space and do the pipe operator and then use the async pipe there, yep, it gives us some auto complete. So that will subscribe or start listening to the value of our counter. So if we get to this -- and like I said, this is kind of the reactive part of it. We set up something, told it what we wanted it to do, and RxJS, it can be mind-bendy at times, but in the end we want to say that when I set up something, I do this, I want this to happen. We've already wired it up, and this is what makes -- or keeps Angular more reactive in that sense. We're just declaring what we want to happen. Then when the data is passed through there, then we get some result out of that. We didn't have to say, okay, if this happens, then set this level, or if this happens, then set that level. We're just saying go through this pipeline.
Jason: So this is a fully derived state, which I think is kind of nice. And it's very clearly derived from the value of state.boops as opposed to something where -- because I feel like what would have happened if it had been me is I would have done something like const boop equals this.store, you know, et cetera to get to the boops value. Then more stuff happens. You're 20 lines later and finally see this piece of logic that's like this.level equals boops is greater than five. So it's harder to track what's going on. This makes it very clear that what we're doing is we're taking something out of the store and deriving a value from it.
Jason: I do like that. I think that's really nice. And we could even make this -- this is just an arbitrary value, right? So we can make that more descriptive.
Brandon: Yep, that'll be however you set up the data, depending on the operators you use, would be how that comes out there. So yeah, definitely gives you that option to be as -- you know, if you want to be verbose with the variables or set up your own kind of pipelines there, that's more of where the power and the mind-bendy part of RxJS comes in at. And this is all the state management part of NgRx. We added the effects package, which is more for side effects. That would be another thing you could do if we were dealing with HTTP calls and things like that. If we wanted to do something with a dom.
Jason: Yeah, let's maybe do that as a way to just show how that works. I think that is one of the things I struggle with a little bit. When we start dealing with two concepts of time flow. We've got the data, which is a stream over time. Then we have async calls happening that don't block the stream over time but have to interact with it. So maybe what we can do here is -- I mean, we can do something really simple. If we clear a certain number of boops, we can send off a call to -- I think there's an API called I can haz dad joke. It doesn't require any kind of authentication. We can just hit it. Let's see. Is there authentication? No authentication is required. We can just hit any dad joke and we get one. Okay. And if we send that as an accept application JSON, we'll get it back as JSON. So that'll work for us.
Brandon: Yeah, so effects is a separate package that we use for side effects, but it kind of integrates in with the store. As before, actions are dispatched, and those cause state changes, but we can also use those to trigger side effects. So if we want to create a new file under boop level, we can create it there or at the top-level folder. Either way.
Jason: We'll just put the joke into the boop-level component so we don't have to create a full other component.
Brandon: Okay. So we can name this boop level.effects.ts.
Brandon: So in here, effects themselves are similar to services. So they're just classes. We want to declare them, import the injectable toke from Angular core. This just tells us that this service can have something injected to it. So that injectable is a decorator. So we do @injectable. Just call that decorator as a function. Yep. Then we're going to export under that decorator. Export a class or boop level effects.
Brandon: So in here what we want to do is inject the action stream. So this is going to give us every action that is dispatched over the lifetime of the application. So we want to inject that into the constructor there. So if we type in constructor and set that one to private, we're going to create another private variable there for actions. The convention is use actions with a dollar at the end of it. If you come across another Angular app and it has that convention, it just means this is a stream of something. This is a stream of actions. Then we put the colon there. Then we want to bring in the actions import from NgRx effects. It'll be that one.
Jason: Okay. I don't know why that got included twice.
Brandon: Okay. So next we want to define -- we can start out with the defining what the level is here. Or create an effect that listens for all the actions. Then we can bring in a state there. We can call -- or make dad joke, I guess. We can set that equal to -- or, this will be just a property on the class. Then set that equal to -- create effect, sorry. So create effect is another -- like I said, all these things are functions. It's a function that takes a callback. What we want to do is return this.actions. So we don't want to save this yet because it's going to cause havoc. So under that on line 12, if we -- after the end of that curly brace, we'll start out with a comma. We'll put an empty object there and set this to dispatch false. So we'll have a property with the name dispatch and set it to false. What that does is saying whenever this effect receives an action, we don't want to pump it back into the store.
Jason: Oh, because otherwise we'd loop.
Brandon: Yeah, otherwise we'd loop. So now if we look at the actions, we can use an operator on here again. Or use a pipe on top of the action stream.
Brandon: So we want to get the -- if I'm understanding correctly, we want to get the latest value from the store and then use that value to determine whether we should make an API call. Is that right?
Brandon: Okay. So we're going to get each action. We may be a little into the weeds here, but we also want to get a value from the store. So we have another operator for that, that lets you get the latest value. If you open up that pipe, and this is maybe one of the more mind-bendy parts, but there's an operator called concat latest from.
Jason: And that comes out of NgRx?
Brandon: It'll be in NgRx effects like the other one. Yep, that's the one. So you can replace that one. Then that takes the callback. So we'll need to inject a store also so we can get that data. If we go back to the constructor and inject the store, then --
Jason: Why didn't it --
Brandon: Inject the store service there.
Jason: There it is.
Brandon: From here, then we'll use that callback to get the latest value from the store. So we'll say this.return. You can return this.store. Then we'll do the select again. We're just going to keep it straightforward here. Select and state, wrapped in parentheses.
Jason: That's right because it needs to be an any.
Brandon: And like I said, normally we would have more type safety here, more selectors and things. That's at least what we need for now. So next, what we want to do is make some decision off of that. We don't want to dispatch that action. So we can just make this API call for now. So next we'll put a comma there.
Brandon: Yes. Then we'll use the tap operator. Like I said, there's many operators you can use at RxJS, but we'll just use that one.
Jason: And tap is functional programming, which means we want to pull the value out without changing it, right?
Brandon: We're going to say whatever comes through there, we're just going to use it and let it go.
Jason: So, because we got the value of the stores, right, then what's going to come back -- what will get passed in is the value of the store, which is our boop count. Is that correct?
Brandon: Actually, this one will give you two things. It'll give you the action that was passed in and the boop count. But it'll be in an array. So it'll give you those two things, yeah, wrapped in an array there.
Brandon: So next we'll say we want to -- and this is where you can define your logic of how you want to make the API request. If you want to do that.
Jason: Can I make this async? Does that work?
Brandon: You don't have to make it async. It'll be synchronous at that point in time.
Jason: Okay. So fetch is async. So if I want to get a response and await this, is it okay to make this one async? Is that going to break everything?
Brandon: It won't break anything. You should be able to do that. We can try that, but I don't think it'll break it.
Jason: Okay. Let's see how this goes.
Brandon: But tap is not expecting a promise there. So it will probably yell at you.
Jason: Okay. So, I want to get my response. I guess we can just do it as like a promise chain. We can
Brandon: Yeah, so the good part is RxJS supports promises. But yeah, we can just do this inline there. What we're doing here is side effects.
Jason: Then the joke. Let's go with full response, and we'll be able to set, like -- I guess we can just say dad joke equals --
Brandon: Yeah, like I said, we're just setting this. This is all happening outside of the store. So if we wanted to pump this back into the store, then we would dispatch an action to do that. But we could just get the response there for this one.
Jason: So if I set it like this, then, and I guess we can say if boop count is less than 10, we'll say, we'll just return. So must have ten boops to ride. We're not going to use the action, so we can just leave the comma there to skip it.
Brandon: So we got the effects registered, but we need to -- or we need to register the effects so they'll run whenever the actions are --
Brandon: So like I said, this is the mind-bendy part. This is -- we haven't modified the store. So we'll need to update the store in order to update the dad joke. So this is happening outside of that loop. I guess in the interest of time, we'll create one more action real quick and use that to -- let's see. We've got to compile.
Jason: What I was thinking is it should only trigger when we increment, right? I guess if we increment or decrement above the value of ten.
Brandon: Right. But the store -- the dad joke is not defined in the store. So we'd have to put the dad joke property in the store to be able to read it.
Jason: Oh! I understand. I understand. Okay.
Brandon: But yeah, we can see if we can crank that out quickly here.
Jason: Yeah, we got like two minutes. (Laughter)
Brandon: So we'll do it really quickly. Let's go to the actions. We'll just throw another action in there really quickly. We'll say add dad joke.
Jason: Got it.
Brandon: And in the reducer, we'll use that -- in the boop.reducer, we'll add that dad joke.
Jason: Okay. So in the reducer, we will say add dad joke.
Brandon: We'll set this -- yeah, I think we're going to run out of time. We got to change the state.
Jason: Oh, we can do it. We can do it.
Brandon: Let's do it. Okay. Count then joke. We'll set that to extreme. Then return -- yell, state.count. We'll have to return new object.
Jason: What don't you like? What don't you like?
Brandon: Okay. In the boop added, we'll return an object there. We'll have to return an object -- sorry, in the reducer. We'll return an object and then set those two values. So it'll be return state count equals zero. Then count equals state.one. And dad joke equal to empty. Like I said, this is part of what we run into. Then in the boop added, we'll obviously have to update that one too. To return the count and the joke.
Jason: Oh, wait. But I got to make this actually --
Brandon: Yeah, you can probably wrap that in parentheses. There you go.
Jason: So that's in parentheses. It does not like this one. Does not exist on -- oh, because it's joke, not dad joke.
Brandon: Oh, right. Yep, there you go.
Jason: So I can fix all these.
Brandon: There you go. So then we've got add dad joke.
Brandon: Then just return. We can set this to a value. Return an object. Then we set the count equal to -- yeah. Then joke will be whatever we want it to dispatch from, from the store here. And on line 18, we're missing a comma there.
Jason: Oh, right. So whatever we want it to dispatch can just be an empty string or something?
Brandon: If we want it to set a string that was coming from the action, we can do that in the action really quickly here. Yeah, you can set it to loading. But this would be whatever the response that comes back from the action is.
Jason: Oh, I got you. So what comes back from the action is going to be --
Brandon: Boop.actions.ts. We would add an argument on the end of create action called props. So that would be import props. That'll be a function that we call. Then you would give that props a type, what we want to add to the action. So we would just say joke -- sorry. After props, we would add, right before you call that method, do the type there. Yep. Add the open and closing arrows.
Jason: Got it.
Brandon: Then object. Then just say joke. Sorry, curly brace, object. Then joke is string. So I have an empty object. Then joke is a string inside of there.
Jason: Okay. I understand. Types like that are not my native language. Okay. So we've got that.
Brandon: Then in the reducer, we can say we'll bring in the action next to the state there.
Jason: Next to the state, okay.
Brandon: Then joke is action.joke. That should give us some auto completion there.
Jason: Okay. So then up here, we need to -- when this is done, we need to dispatch?
Brandon: Yes. So we can do this a couple of ways. You can dispatch -- I guess in the interest of time, we'll dispatch the joke and send the joke along with it.
Jason: So this.store.dispatch. We'll say add dad joke.
Brandon: You'll call it like a function. You'll call add dad joke like a function. Then you'll pass the -- you'll create an object inside of add dad joke. Then set the joke with the property of the response. So it'll be joke, colon, response.
Jason: Because it's an object. Right, okay.
Brandon: There you go.
Jason: So theoretically speaking, this works.
Brandon: Well, we'll have to wire the effects. If we go back to the app module really quickly and type in effectsmodule.forroot. We still use that same convention. That'll be an array inside of there. Then we'll use the boop level effects, or boop level effects class. So that registers the effects that we created.
Jason: Doesn't like something.
Brandon: Okay. Let's see. Let's go back to the command line.
Jason: Error occurs in the template of component boop level component. Doesn't like dad joke. That's because it's not called dad joke anymore. It is called joke.
Jason: No, it's not. What is it called?
Brandon: We'll define it. So in the boop level component, we will define a property in there called joke. Then we'll say -- we'll set that equal to this.store.select. We'll want to remove the string type there. It'll just infer the type for you.
Jason: That's easier.
Brandon: Yep. Then we'll use state, any. Then state.boops.joke. Because we had to change it to an object.
Jason: Gotcha. Then we need to make that one into async, right?
Brandon: Yes. It will be in the boop component.
Jason: Holy crop. It's working, but I'm now de-dossing this thing. What's my boop level? All my boops are now --
Brandon: Oh, let's see.
Jason: We busted that. Let's go to the component itself, the HTML. So this one is no longer the count.
Brandon: Yeah, it'll be state.count.boops.
Jason: On line 17. Yeah, boops.count.
Brandon: Or count.boops.
Jason: So it's still running without --
Brandon: Let's see. Let's go back. Did we take the dispatch false off there?
Jason: Dispatch false is here.
Brandon: Okay. Still there. Let's go up to --
Jason: Is our boop count coming in? Does it need to be boopcount.count?
Jason: There we go. Okay. So it starts to explode. We need an escape hatch. If we've already requested it, we'd set a flag. That's fine. We're over time. So, let's do this. Instead of -- this is good enough, right. We proved that it works. We would need to set like a flag in here that said, you know, if a joke hasn't been requested and then do another check that if we go below nine, to clear that flag. That part is just logic. I get that part. But this is super powerful stuff. If somebody wants to learn more, where should they go next?
Brandon: Yeah, if you want to learn more about NgRx, you can go to NgRx.io. We have docs there that show you, kind of walk through the whole ecosystem of libraries here. We talked about store and effects. There are other libraries for -- like if we wanted to do more granular, local state, there's component store for that. Also, ones for hooking in with Angular router and managing collections is another one.
Jason: Oh, nice.
Brandon: And even ones for if you're managing a lot of collections and you want all that data wrapped up for you, there's inject data there.
Jason: Lots of stuff, yeah.
Brandon: A lot of stuff you can choose depending on how much you want to get out of the platform.
Jason: Great. And if you want to ask questions directly to the maintainer, go follow Brandon on Twitter. Let me do a quick shout to our live captioning. Rachel, who is staying late with us today. Thank you so much, Rachel. Sorry for going over. She's here from White Coat Captioning. White Coat Captioning is made possible through the generous support of our sponsors. We've got Netlify, Fauna, Hasura, and Auth0 all chipping in to make this show more accessible to more people. While you're checking out the homepage, head over to that schedule. We have so much good stuff coming up. We're coming back on Thursday. We're going to figure out how to bring any data source into GraphQL using StepZen with Carlos Eberhardt. Click that add to Google Calendar button. It doesn't take your email or anything, just letting you know when things are happening. Dang. All right. Lightning round exit. Brandon, anything you want to add before we call this done?
Brandon: A quick shout out here. We talked about Nx at the jump here. We're doing a conference for Nx, September 16th and 17th. You can go to nx.dev/conf to find out more about that. It's free. Got to throw that out there, too. Feel free to register that ticket if you want to learn more about Nx and how you can build some cool stuff with that. Definitely check that out.
Jason: Excellent. All right. On that note, we're going to call this one done. Chat, stay tuned. We're going to go raid TypeScript Tea Time. Brandon, thank you so much for all of your time today. Really appreciated it. We will see y'all next time.