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.
- Component requests data from service
- Service fetches data from Firebase
- 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:
- Component dispatch the action: FETCH_DATA
- Store receive the FETCH_DATA action
- @ngrx/effects is triggered by FETCH_DATA action
- @ngrx/effects gets data from Service
- @ngrx/effects dispatch the action: FETCH_DATA_SUCCESS
- Store is updated
- Data flows to Component
Code
Code section consists of 5 parts:
- Project Setup
- Firebase Setup
- Store Setup
- 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 stateexport interface State {
loading: boolean; // indicates loading while fetching data
events: Event[];
}// Define initial stateconst initialState: State = {
loading: false,
events: []
};// reducer functionexport 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.