Getting Started with @ngrx and Firebase

In this tutorial we will explore how to use @ngrx and Firebase together in a simplest way. Please check this article first if you are not familiar with @ngrx.

Anatomy of the App

In this section, we will try to explain the app architecture in brief.

Here are the basic events in the app.

  1. Component requests data from service
  2. Service fetches data from Firebase
  3. Component dispatch an action to Store with data

But we don’t want our component to handle async calls via service. Our component should only dispatch actions to store, and store should handle async calls.

We should use @ngrx/effects library to enable Store to handle async calls. So let’s integrate @ngrx/effects module into our store and see how it looks like.

Now our components don’t have to care about services/backend, Components request/receive data from store and display data.

If we look Store and Effects in detail, we see that Effects subscribe to store and listens to the actions then dispatches new actions according to previous actions.

Here are the updated version of the events after @ngrx/effects is integrated into the app:

  1. Component dispatch the action: FETCH_DATA
  2. Store receive the FETCH_DATA action
  3. @ngrx/effects is triggered by FETCH_DATA action
  4. @ngrx/effects gets data from Service
  5. @ngrx/effects dispatch the action: FETCH_DATA_SUCCESS
  6. Store is updated
  7. Data flows to Component

Code

Code section consists of 5 parts:

  1. Project Setup
  2. Firebase Setup
  3. Store Setup
  4. Component

1.Project Setup

Let’s create new project by using angular-cli.

ng new firebase-ngrx
cd firebase-ngrx

then install firebase, @ngrx/store and @ngrx/effects

npm install --save @ngrx/core @ngrx/store @ngrx-effects
npm install --save angularfire2 firebase

2. Firebase Setup

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { AngularFireModule } from 'angularFire2'
import { AngularFireDatabaseModule } from 'angularFire2/database'
export const firebaseCredentials = {
apiKey: '',
authDomain: '',
databaseURL: '',
projectId: '',
storageBucket: '',
messagingSenderId: '878805862123'
}
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AngularFireModule.initializeApp(firebaseCredentials),
AngularFireDatabaseModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

Let’s create a service to handle actual async calls.

ng generate service firebase // genereates ./app/firebase.service.ts

Then let’s add required Firebase elements into the firebase.service.ts.

import { Injectable } from '@angular/core';
import { AngularFireDatabase,
FirebaseListObservable } from 'angularfire2/database';
@Injectable()
export class FirebaseService {
event: FirebaseListObservable<any[]>;
constructor(private db: AngularFireDatabase) {
this.items = this.db.list('/events');
}
}

3. Store Setup

a) Actions

Our app is designed to fetch an event list from firebase and display them. So we will show only two actions in this post: FETCH_EVENTS and FETCH_EVENTS_SUCCESS

import { Injectable } from '@angular/core';
import { Action, Store } from '@ngrx/store';
import { Event } from '../models';
export const FETCH_EVENTS = 'Fetch EVENTS';
export const FETCH_EVENTS_SUCCESS = 'Fetch EVENTS Success';
export class FetchEvents implements Action {
readonly type = FETCH_EVENTS;
constructor() { };
}
export class FetchEventsSuccess implements Action {
readonly type = FETCH_EVENTS_SUCCESS;
constructor(payload: Event[]) { };
}
export type Actions = FetchEvents | FetchEventsSuccess;

b) Reducer

import * as EventActions from './actions';
import { Event } from '../models';
// Define state
export interface State {
loading: boolean; // indicates loading while fetching data
events: Event[];
}
// Define initial state
const initialState: State = {
loading: false,
events: []
};
// reducer function
export function reducer( state = initialState,
action: EventActions.Actions ): State {
   switch (action.type) {
case EventActions.FETCH_EVENTS: {
return {
...state,
loading: true
}
}

case EventActions.FETCH_EVENTS_SUCCESS: {
return {
loading: false,
events: action.payload
}
}
default: {
return state
}
}
}

c) Effect

If FETCH_EVENTS is dispatched to store, then retrieve the firebase items and dispatch a new action with payload. Here is the core part of an Effect.

@Effect()
FetchEvents$: Observable<Action> = this.actions$.ofType(actions.FETCH_EVENTS) // filtering actions
.switchMap(() => this.firebaseService.items
.do((payload) => new actions.FetchEventsSuccess(payload))
);

d) Store

We import StoreModule and EffectsModule into app.module.ts as following.

app.module.ts

import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { reducer } from './store/reducer';
import { EventEffects } from './store/effects';
@NgModule({
declarations: [
AppComponent
],
imports: [
...
StoreModule.provideStore(reducer),
EffectsModule.run(EventEffects),

],
providers: [FirebaseService],
bootstrap: [AppComponent]
})
export class AppModule { }

4) Component

We have integrated Store, Effects, Service and Firebase successfully.

All we need to do is to open a communication channel between Store and Component. Store.select() and Store.dispatch() methods will be used for the communication.

app.component.ts

import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import * as reducer from 'app/store/reducer';
import * as Actions from 'app/store/actions';
import { Observable } from 'rxjs/Observable';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
events: Observable<any[]>;
constructor (private store: Store<reducer.State>) {}
ngOnInit() {
  this.store.dispatch(new Actions.FetchEvents());
this.events = this.store.select(state => state.events);
}
}

app.component.html

...
<md-card *ngFor="let event of events | async">
<img md-card-image [src]="event.picture">
<md-card-header>
<md-card-title>Event name: {{event.name}}</md-card-title>
<md-card-subtitle>Location: {{event.location}}</md-card-subtitle>
</md-card-header>
<md-card-content>Description: {{event.description}}</md-card-content>
</md-card>
...

Final result

Conclusion

In this tutorial we learn how to use firebase with @ngrx/store and @ngrx/effect to enable our application to simply manage firebase operations. Only the database query operation is covered in this post but other firebase operations such as update, remove, file upload can be implemented using same the principle.

Hopefully this article would help you in your own project.