Search Lessons, Code Snippets, and Videos
search by algolia
X
#native_cta# #native_desc# Sponsored by #native_company#

Angular Ngrx Effects With the Firebase Realtime Database

Episode 44 written by Jeff Delaney
full courses and content on fireship.io

If you’re brand new to Redux patterns in Angular, check out the NgRx Quick Start Lesson before diving into this lesson. It covers the basic concepts that this article only brushes over.

In this lesson, we will integrate the Firebase Realtime Database with @ngrx/effects for Angular 4. The app itself is very simple - it retrieves a post from Firebase and allows the user to update it’s vote total by upvoting or downvoting it . You can obtain the full source code for ngrx-fire on github. The goal is not to build a real-world app, but rather show you how ngrx effects can be used with Firebase.

This lesson assumes you have a basic Angular app generated with the Angular CLI with AngularFire2 configured as the backend.

demo of Firebase database with Angular ngrx redux

NgRx Effects Overview

Effects are used in Redux to handle the side effects of impure functions that affect data outside their scope - such as requests to the Firebase Database. When performing operations in Firebase, we get a Promise that will either succeed or fail. That means we have at least three possible states for a Firebase operation, such as LOADING, SUCCESS, or FAIL. Ngrx effects gives us a way reconcile the side effects of these various states throughout the course of an API call.

Pure Functions vs Impure Functions

To get a better idea of this concept, let’s compare pure and impure functions in JavaScript.

A return value of a pure function is affected only by its own logic and arguments. Consider the following example:

const pure = (hello) => {
return hello + 'world'
}

Now lets write an impure function. Notice how it takes the value of variable outside of its scope to create it’s return value.

let hello = 'Howdy!';

const impure = () => {
return hello + 'world'
}

In app development, most impure function are the result of callbacks, Promises, making calls external APIs, or changing data in some other asynchronous process.

const impure = () => {
updateDataBase();
return hello + 'world'
}

Building an NgRx App with Firebase

This section will cover four different elements of an ngrx Redux app, including the model, actions, reducer, and effects.

app.module.ts

First things first, let’s get import the necessary ngrx pieces in the NgModule.

import { environment }               from '../environments/environment';
export const firebaseConfig = environment.firebaseConfig;

import { AngularFireModule } from 'angularfire2';
import { AngularFireDatabaseModule } from 'angularfire2/database';


import { EffectsModule } from '@ngrx/effects';
import { StoreModule } from '@ngrx/store';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';

import { PostEffects } from './effects/post.effects';
import { postReducer } from './reducers/post.reducer';


@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,

AngularFireModule.initializeApp(firebaseConfig),
AngularFireDatabaseModule,

EffectsModule.forRoot([PostEffects]),

StoreModule.forRoot({
post: postReducer
}),

StoreDevtoolsModule.instrument({ maxAge: 25 })
],
bootstrap: [AppComponent]
})
export class AppModule { }

post.model.ts

Our post model is just a TypeScript interface that will serve as the blueprint for our Post data. When working with ngrx, it is highly recommended to that you model your data with TypeScript interfaces to ensure the app data store adheres to the intended structure.

export interface Post {
pushKey: string;
loading: boolean;
text: string;
votes: number;
error?: string;
}

post.actions.ts

The only way to change the state of ngrx Store is by dispatching an action. In this example, we are giving each action its own class along with a payload in the constructor. This allows us to send a data payload to the action.

import { Action } from '@ngrx/store';
import { Post } from '../models/post.model';

export const GET_POST = 'Post get';
export const GET_POST_SUCCESS = 'Post get success';

export const VOTE_UPDATE = 'Post Vote';
export const VOTE_SUCCESS = 'Post Vote success';
export const VOTE_FAIL = 'Post Vote fail';


export class GetPost implements Action {
readonly type = GET_POST;
constructor(public payload: string) {}
}

export class GetPostSuccess implements Action {
readonly type = GET_POST_SUCCESS;
constructor(public payload: Post) {}
}

export class VoteUpdate implements Action {
readonly type = VOTE_UPDATE;
constructor(public payload: any) {}
}

export class VoteSuccess implements Action {
readonly type = VOTE_SUCCESS;
constructor(public payload?: any) {}
}

export class VoteFail implements Action {
readonly type = VOTE_FAIL;
constructor(public payload?: any) {}
}



export type All
= GetPost
| GetPostSuccess
| VoteUpdate
| VoteSuccess
| VoteFail;

post.reducer.ts

The reducer function is pretty simple. It just creates a new object by copying the old state, then overwriting any properties that have changed. It updates the data payload when necessary and toggles the loading property.

Our reducer takes advantage of the spread syntax in JavaScript compose a new state object, i.e { ...oldState, ...newValues }. This syntax is an alternative to Object.assign.

import * as PostActions from '../actions/post.actions';
import { Post } from '../models/post.model';

export type Action = PostActions.All;

/// Reducer function
export function postReducer(state: Post, action: Action) {

switch (action.type) {

case PostActions.GET_POST:
return { ...state, loading: true };

case PostActions.GET_POST_SUCCESS:
return { ...state, ...action.payload, loading: false };

case PostActions.VOTE_UPDATE:
return { ...state, ...action.payload, loading: true };

case PostActions.VOTE_SUCCESS:
return { ...state, loading: false };

case PostActions.VOTE_FAIL:
return { ...state, ...action.payload, loading: false };

default:
return state;

}
}

post.effects.ts

The purpose of ngrx/effects is to merge action streams, making it possible for you to subscribe to them. It does this by exporting an Actions observable that emits all actions dispatched in the app.

For example, the calling actions.ofType('VOTE_UPDATE') will set that action as the root, then it will emit the other actions VOTE_SUCCESS or VOTE_FAIL as they occur asynchronously.

I am using the RxJS delay(2000) to show the loading spinner, but is not required for the code to work. Firebase is too fast, so I manually added a delay.

import { Injectable }                 from '@angular/core';
import { Effect, Actions } from '@ngrx/effects';
import { AngularFireDatabase } from 'angularfire2/database';

import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/delay';

import * as postActions from '../actions/post.actions';
export type Action = postActions.All;


@Injectable()
export class PostEffects {

constructor(private actions: Actions, private db: AngularFireDatabase) {}

@Effect()
getPost: Observable<Action> = this.actions.ofType(postActions.GET_POST)
.map((action: postActions.GetPost) => action.payload )
.delay(2000) // delay to show spinner
.mergeMap(payload => this.db.object(payload))
.map(post => {
post.pushKey = post.$key;
return new postActions.GetPostSuccess(post);
});


@Effect()
voteUpdate: Observable<Action> = this.actions.ofType(postActions.VOTE_UPDATE)
.map((action: postActions.VoteUpdate) => action.payload )
.mergeMap(payload => of(this.db.object('posts/' + payload.post.pushKey)
.update({
votes: payload.post.votes + payload.val
})))

.map(() => new postActions.VoteSuccess())
.catch(err => of (new postActions.VoteFail( { error: err.message } )) );
}

Putting It Together in the App Component

Now that all of the ngrx code is in place, we just need to give the user a way interact with it in the UI in Angular.

ngrx redux debugger with Firebase data

app.component.ts

We can retrieve a post from Firebase and update it in the ngrx store by simply dispatching root action.

import { Component } from '@angular/core';

import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';

import { Post } from './models/post.model';
import * as postActions from './actions/post.actions';

interface AppState {
post: Post;
}

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {

post$: Observable<Post>;

constructor(private store: Store<AppState>) {
this.post$ = this.store.select('post');
}

getPost() {
this.store.dispatch(new postActions.GetPost('/posts/testPost'));
}

vote(post: Post, val: number) {
this.store.dispatch(new postActions.VoteUpdate({ post, val }));
}


}

app.component.html

In the html, we can just subscribe to the post and attach the dispatch functions to button clicks. I am also using FontAwesome to display a loading spinner when the post state is loading.

<div *ngIf="post$ | async as post">

<i *ngIf="post.loading" class="fa fa-spin fa-refresh"></i>

<h2>{{ post.text }}</h2>
<h4>Votes: {{ post.votes }}</h4>

<button (click)="vote(post, 1)">Upvote</button>
<button (click)="vote(post, -1)">Downvote</button>

</div>


<button (click)="getPost()">Load Post from Firebase</button>

Up Next

I plan on producing another ngrx lesson that will cover Firebase Auth with ngrx in the near future. If you have suggestions, please let me know in the comments or via our Slack team.