Saturday, October 8, 2016

Getting started with AngularFire 2

The blog describes using AngularFire2, Angular 2 and TypeScript API for Firebase. It is a beginner guide with a sample for retrieval and update to Firebase database.
Blogger: V. Keerti Kotaru . Author of Angular Material book . Twitter @KeertiKotaru . linkedin.com/in/keertikotaru

Firebase started as a cloud database that can store and retrieve JSON objects. It provided an effective database solution for mobile apps and other applications. Today if you look at the newer version of Firebase, database is only a part of it. It has great analytics features, push notifications to mobile & Chrome and Firebase Cloud Messaging (FCM).

Realtime Database

Firebase database has a unique feature to synchronize database and clients systems automatically. In case of Web UI, it uses a Web Socket connection to push changes to the client. As and when there is a change to the JSON data on the cloud DB, it is pushed to all connected clients.

Sample - Bus Schedule Management: For the blog, I built a couple of pages that deal with bus schedule management. A page to list buses and schedule. And another page to update if there is delay in the arrival time for a bus. 

Consider figure 1. The window on the left shows list of buses to the passengers. The window on the right could be used by admins to update if there is a delay in arrival. The sample is using AngularFire2 (Firebase API for Angular2 & Typescript) to connect with the Firebase database. As and when there is a change to ETA (Expected Time of Arrival), it's instantly synchronized with all clients.

Checkout complete code sample here..


Figure 1: Real time updates
Figure 2: A Sample Firebase App
Left Nav with feature List.

This blog uses AngularFire 2 & Typescript for the Firebase API. Firebase integrates with multiple platforms including Android, iOS and Web. While the Web JavaScript API is for plain JS that could be use in any HTML/JS app, the AngularFire is AngularJS specific API.

AngularFire 2 is in beta at the time of writing this blog. It uses Angular 2. And code in this blog uses TypeScript along with Angular.

Getting Started - Create an app on Firebase console

  • Log into firebase at firebase.google.com. Sign-up if you don't have an account already.
  • Once logged-in,  click on "Go to console".
  • It lists an existing Firebase apps. If it's a new account create an app. 
  • Click on the app to see various features provided by Firebase.

Create Angular 2 Project

Angular CLI is preferred tool for scaffolding an Angular 2 application. Create a new Angular2 app using the following command. It will also install the dependencies.


ng new angular-fire-2-sample

Note: If you do not have Angular CLI already installed, follow the link to get instructions on installing the tool. 


Add angularfire2 and firebase package references


To use AngularFire2 and firebase API in the project, install the package using the following command.


npm install firebase angularfire2 --save
(--save will update to package.json for future installation of required dependencies)


Add AngularFire2 app to Angular Module

In the scaffolded project the main module is in the file  src/app/app.module.ts
Edit this file to import AngularFireModule from angulafire2
import {AngularFireModule} from 'angularfire2';


Create configuration object. 

// API documentation suggests to export the configuration
export const config = { 
 apiKey: "[API Key]", 
 authDomain: "gdg-bustracker.firebaseapp.com", 
 databaseURL: "https://gdg-bustracker.firebaseio.com", 
 storageBucket: "" 
};

Follow below steps to create the configuration object readymade.
  1. Click on settings icon next to the app in Firebase Console.
  2. Click on "Add Firebase to you Web App" link. It presents the configuration object.
  3. Copy the configuration to the AngularFire app.
Figure 3: Two easy steps to get to Firebase configuration to be added to the Web App
Add reference to the module in the module imports. Consider following code,
@NgModule({ 
 declarations: [ AppComponent ], 
 imports: [ 
       BrowserModule, 
       FormsModule, 
       HttpModule, 
       AngularFireModule.initializeApp(config) // initializes and import Firebase module     ], 
 providers: [], 
 bootstrap: [AppComponent] 
})

Note: There is an open bug, that could result in build errors with firebase package. Add the following line in src/main.ts to circument the problem temporarily.

import * as firebase from 'firebase';

Now Firebase API is ready to use.

Create a service to integrate with Firebase

It is a good idea to keep the bus data access in a separate service. It could be injected in components while dealing with bus data.

Create the service using Angular CLI with the following command.

ng g service bus-data-access

The generated class is named BusDataAccessService. Provide the service in the main module (app.module.ts). Consider following code snippet,
// import the service module 
import { BusDataAccessService } from './shared/bus-data-access.service'; 

// add it to providers list in the module. Note that the @NgModule decorator is stripped off additional details for readability. Look at the file in github for complete code. @NgModule({ 
 declarations: [ ], 
 imports: [ AngularFireModule.initializeApp(config) ], 
 providers: [ BusDataAccessService], // *** the service is provided here (to the module)
 bootstrap: [AppComponent] }) 
export class AppModule { }

Import the following in the newly created bus-data-access.service.ts file,
import { AngularFire, FirebaseListObservable } from 'angularfire2';
  • AngularFire - provides API for various firebase services. In the BusDataAccessService, we use it to interact with database.
  • FirebaseListObservable - An RxJS observable. The BusDataAccessService returns the observable (list of buses) which could used in the template. Please note, the bindings in the template are asynchronous with observables.

Retrieve the bus list

Consider following code in the BusDataAccessService for retrieving bus list.
// Class property for bus list 
 buses: FirebaseListObservable; 

 // inject AngularFire service 
 constructor(firebase: AngularFire) { 
   // get bus list from schedule node on the Firebase DB. 
   this.buses = firebase.database.list("/schedule"); 
}

We are retrieving bus list from a node named schedule in the JSON stored on Firebase DB. Here is the structure of bus object I have. The FirebaseListObservable object yields a list of these objects.
"A2F001": { 
   "from": "Hyderabad", 
   "to": "Bengaluru", 
   "expectedTimeOfArrival": "10/02/2016 10:00", 
   "scheduledTimeOfArrival": "10/02/2016 10:00", 
   "delay": 0, 
   "delayReason": "N/A" 
 }

A function getBusList on BusDataAccessService returns the observable.
getBusList(){ 
  return this.buses; 
}


Update changes to Bus Schedule

We use another function in BusDataAccessService for updates to the bus schedule. Consider following code.
  saveBusData(id, expectedTimeOfArrival, delay, delayReason){
    this.buses.update(id, {
      expectedTimeOfArrival:expectedTimeOfArrival,
      delay:delay,
      delayReason: delayReason
    })
  }

In the given application I anticipate changes to three properties, expected time of arrival, delay in minutes and delay reason. The save function expects these as parameters.

We also need to know which bus schedule is being updated. The first parameter, id should have the unique identifier for the bus JSON object.

The update API (in AngularFire) expects field being updated as a key and the new value as the value. We are using ES2015 syntaxes. When the key and value variable names are the same (on the JSON object), we don't have to write them twice. Will cleanup this payload as the following.


  saveBusData(id, expectedTimeOfArrival, delay, delayReason){
    this.buses.update(id, {
      expectedTimeOfArrival,
      delay,
      delayReason
    })
  }


Integrate the BusDataAccessService with the components

Component shows the bus data on the screen. Component calls the above written getBusList() of BusDataAccessService.

As a first step, import the service in bus-list.component.ts
import { BusDataAccessService } from '../shared/bus-data-access.service';

In the constructor inject the service and call getBusList function. The returned data is assigned to a field buses on the class.
  constructor(dataService: BusDataAccessService){
    this.buses = dataService.getBusList();
  }

In the template iterate through buses asynchronously. Refer to async filter on the *ngFor. Note that the field buses is an observer. Unlike an array, whole list is not available in an observer upfront. Each record is asynchronously obtained.

Consider following template snippet from bus-list.component.html 
<div *ngFor="let item of schedule | async">
        <h3 class="panel-title">{{item.from}} to {{item.to}}</h3>
        <!-- other similar bindings go here. Refer to file in github for complete template. -->
</div>


Make updates to bus data

 In the sample repo, another component admin has controls to update delay information. Refer to figure 1, which let's user add delay in minutes and reason for the delay. The admin component has similar template to that of bus list, with additional controls to increment/decrement minutes and a text area to input reason for the delay.

As user updates delay information and clicks on the save button, following handler is called in the component class. It in-turn calls the saveBusData function in BusDataAccessService, which is using the Firebase API to update the database.
   save(item){
    this.busDataAccess.saveBusData(item.$key,
      item.expectedTimeOfArrival,
      item.delay,
      item.delayReason
    );
   }

Template for the button in admin component
<button class="btn btn-info" (click)="save(item)">Save</button>

References and further reading

AngularFire 2 Github
Follow this link for complete code sample
Firebase Docs