A place to obtain the wisdom of Jim.
If you haven't already read my first post about setting up your angular 2 project with Ngrx/store then you should check that out first. In this post we will build on this state management architecture by learning about how to to think about asynchronous actions in the world of ngrx. We'll start with some simple examples and ultimately build up to @Effects that pull data from Firebase database. Let's get started!
You can view the code files for a full ngrx example project that uses both ngrx/store and ngrx/effects here:
In a simple ngrx/store project without ngrx/effects there is really no good place to put your async calls. Suppose a user clicks on a button or types into an input box and then we need to make an asynchronous call. The dumb component will be the first to know about this action from the user, and it's handler will be called when the button is actually clicked. However, we don't want to put the logic to do our async call right in the dumb component since we want to keep it dumb! The only thing in the dumb component's handler is it's @Output emitter emitting an event to the smart component telling it that the button was clicked. Then the smart component gets the event and it's handler function is triggered, but we don't want to put the async login right in there because we want to keep it lean and only dipatching actions to our store so that the store can modify the state! Ok... but the store only handles actions in the reducer, and reducer are meant to be pure functions so where are we supposed to logically put our async calls so that we can put their response data in the store? The answer, friends, is @Effects! You can almost think of your Effects as special kinds of reducer functions that are meant to be a place for you to put your async calls in such a way that the returned data can then be easily inserted into the store's internal state for the application.
A Separate Service For Async
You might be thinking, "What if you have the smart component just communicate with another service that calls for async data, and then when that call comes back the service dispatchs an event to the store with the returned data as a payload?", and in a way you'd be right! In Angular 2 a service is just a regular old TypeScript class with the @Injectable metadata, and when working with @Effects you make a single "Effect Class" or "Effect Service" that then contains various @Effect functions, each corresponding to an action dispatched by your ngrx store.
The first thing you'll need to do is install @ngrx/effects via npm:
Add RunEffects To Your NgModule
Next, you need to tell your application that you wan to use effects. In the imports array in your NgModule add a line where you call EffectsModule.run. Pass in the class (or classes) that you are using as the "Effects Class" (or classes). Of course your NgModule file will probably have lots more code in in thie file, but I've distilled it down to just the ngrx things here:
Create An Effects Class
The name of this class should be the same as what you reference in your NgModule step above. At it's core, the Effects Class in simply just an Angular 2 Service:
Hello World @Effect
Now that you've got all the setup out of the way, let's get down to writing some effects!
Wow! Don't be freaked out! I know it looks very weird at first, but let's go through it. We're using the TypeScript metadata to label our variable update$ (the $ is commonly used as a suffix for variables whose value is an observable) as an "ngrx effect" that will be triggered when we dispatch actions with the store (the same was we always send actions to the reducer or reducers). Then we see "this.action$.ofType('SUPER_SIMPLE_EFFECT')". Remeber, we're translating the dispatched event into an observable, and .ofType means you're taking in an observable and then returning the observable only if it's of that type. Then we do switchMap because we want to "switch over" from the original observable to a brand new observable. What you want to return from an ngrx/effect is an observable to an action, and when it all plays out on screen we'll first have the intial action dispatched from the component (or some service). It will then bypass the reducer and be handled by an effect. This effect will then return an observable to some action, and the new action will be handled in the reducer.
In this next example we get a little bit fancier by dealing with payloads. We can both accept a payload from the initial action and return a payload. In the code below immediately after we get an action obervable of type "SEND_PAYLOAD_TO_EFFECT" we call "map(toPayload) on it. So we take in an observable with an action and a payload containing the stuff we care about, and we return an Observable with just the payload. Then we do a switchMap because we want to switch over to our response Observable, but we still have that payload as an argument in our switchMap function. You can then see that, following a very Redux-ish pattern, we have an object with a type and a payload. The payload can be an object containing basically whatever you want. We then return an Observable to a "PAYLOAD_EFFECT_RESPONDS" action with a new payload message, and we're done!
Async Effect With A Timer
Pulling Data From Firebase With AngularFire2
I personally like use Firebase a lot. It's a NoSQL database from Google that's totally hosted and backed up for you, has super quick read/write operations, and is easy to use. The AngularFire2 library fits in especially awesomely here because it allows you to query your database in a way such that the result is an observable. First, make sure you have it installed:
Async Effect Pull Array From Firebase
Ok, let's jump into an @Effect that pulls data from firebase! So we get an action, we see that it's of type PULL_ARRAY_FROM_FIREBASE, and then we switchMap over so we can start a new Observable. Here's where our async call comes in! In this case we're using the Firebase Realtime Database, and the slick AngularFire2 library gives us a very nice api for pulling a section of our database as an array. The key thing to realize here is that in the AngularFire2 library af.database.list returns an Observable! The string you pass in allows you to "drill down" into your NoSQL JSON object data store to pull some given node as an array. Next, we switchMap over to a new Observable, the one we want to return. We'll then return an Observable to an action of type, "GOT_FIREBASE_ARRAY" with a payload that contains the array we got back from Firebase so we can send the data back over to our reducer which can then update the state.
Async Effect Pull Object From Firebase
SwitchMap Just Takes A Function
We're using a lot of switchMaps here so it's important to understsand the magic behind it. Here's a screenshot from the reactive.io switchMap docs page:
Look at the descriptiong here. The first (and often only) parameter to switchMap is a function that is applied to an item emitted by a source Observable and returns an Observable. It's also worth noting that the fat arrows we have here are really just shorthand for saying that we're making a function. Since the one observable is the only thing in the body of the function and it's only a single line we can omit the curly braces and the return keyword. For example, aside from some extra logging this is code below is identical to the previous snippet above:
Although the former example is more concise, and you will probably refactor down to that at some point, look at this example directly above may help you understand what's really going on in the code.
The posts on this site are written and maintained by Jim Lynch. About Jim...