Part 2 of The Angular 2 Ngrx Series

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!

Source Code

You can view the code files for a full ngrx example project that uses both ngrx/store and ngrx/effects here:
https://github.com/JimTheMan/Jims-Ngrx-Example

Why @Effects?

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.

Installing Ngrx/Effects

The first thing you’ll need to do is install @ngrx/effects via npm:

npm install @ngrx/effects --save

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:

import { StoreModule } from '@ngrx/store';
import { MainStoreReducer } from './state-management/reducers/main-store-reducer';
import { EffectsModule } from '@ngrx/effects';
import { MainEffects } from "./state-management/effects/main-store-effects";
@NgModule({
imports: [
StoreModule.provideStore({mainStoreReducer}),
EffectsModule.run(MainEffects),
]
]
})
export class AppModule { }

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:

import {Effect, Actions, toPayload} from "@ngrx/effects";
import {Injectable} from "@angular/core";
import {Observable} from "rxjs";
@Injectable()
export class MainEffects {

constructor(private action$: Actions) { }
}

Hello World @Effect

Now that you’ve got all the setup out of the way, let’s get down to writing some effects!

@Effect() update$ = this.action$
.ofType('SUPER_SIMPLE_EFFECT')
.switchMap( () =>
Observable.of({type: "SUPER_SIMPLE_EFFECT_HAS_FINISHED"})
);

Wow! Don’t be freaked out! I know it looks very weird at first, but let’s go through it. Were 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, so .ofType means your taking in an observable and then returning the observable only if it is 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 the intial action will be 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 then be handled in the reducer.

Payload Example

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” call “map(toPayload) on it. So we take in an observable with an action and a payload a bunch of other stuff, 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 ourswitchMap 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 that as an observable, and we’re done!

@Effect() effectWithPayloadExample$ = this.action$
.ofType('SEND_PAYLOAD_TO_EFFECT')
.map(toPayload)
.switchMap(payload => {
console.log('the payload was: ' + payload.message);
return Observable.of({type: "PAYLOAD_EFFECT_RESPONDS", payload: {message: "The effect says hi!"}})
});

Async Effect With A Timer

Our next barrel of fun contains a timer. This is similar to the “setTimeout” as you might have seen it before in JavaScript. However, we want this timer to return, of course, and observable so we’ll use Observable.timer(). Notice that we’re taking in a payload as the number of seconds to set on the timer. The key thing to realize is that where we would normally have a callback function after asynchronous event we now just switchMap off of it. Once the timer completes then we return an observable to an action “TIMER_FINISHED” which then get’s handled in the reducer.

@Effect() timeEffect = this.action$
.ofType('SET_TIMER')
.map(toPayload)
.switchMap(payload =>
Observable.timer(payload.seconds * 1000)
.switchMap(() =>
Observable.of({type: "TIMER_FINISHED"})
)
)

Pulling Data From Firebase With AngularFire2

I personally like use Firebase a lot. This is a NoSQL database from Google that’s super high performance and 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:

npm i angularfire2 --save

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. 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.

@Effect() pullArrayFromFirebase$ = this.action$
.ofType('PULL_ARRAY_FROM_FIREBASE')
.switchMap( () =>
this.af.database.list('/cypherapp/rooms/')
.switchMap(result =>
Observable.of({type: "GOT_FIREBASE_ARRAY", payload: {pulledArray: result}})
)
)

Async Effect Pull Object From Firebase

So, now we know how to pull a node from Firebase as an array, but what if we want to pull it as a regular JavaScript Object? Well, all we need to do is change af.database.object instead of af.database.list, ​and the rest of the code is exactly the same!

@Effect() pullObjectFromFirebase$ = this.action$
.ofType('PULL_OBJECT_FROM_FIREBASE')
.switchMap( () =>
this.af.database.object('/cypherapp/rooms/')
.switchMap(result =>
Observable.of({type: "GOT_FIREBASE_OBJECT", payload: {pulledObject: result}})
)
)

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:

@Effect() pullObjectFromFirebase$ = this.action$
.ofType('PULL_OBJECT_FROM_FIREBASE')
.switchMap(() => {
console.log('in the first switchMap!'); return this.af.database.object('/cypherapp/rooms/')
.switchMap(result => {
console.log('oh yeah, we got the result!'); return Observable.of({type: "GOT_FIREBASE_OBJECT", payload: {pulledObject: result}})
})
});

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.

--

--