Angular JS and Firebase

Create an application using Angular JS and Firebase

Written by 42ID on November 16 2014

Click here to view presentation slides. (press N for next slide and P for previous slide)

Stay tuned for screen cast. Sign up to receive notification when they are posted.

Introduction

We are going to create an application to showcase Angular JS with Firebase. For this example we will create an application that will enable food truck operators to enter information about their daily specials as well as automatically tag their geo location when they make an entry. We'll also hook into Google Maps to display their locations on a map

Firebase is a cloud based document database that allows for real-time syncing of data between the database and all of the clients that are connected to it. The clients can be any kind of devices, whether it is mobile phone, desktop, any thing that connects to it will receive real time updates when the data changes -- when combined with the power of AngularJS, this enables us to write rich interactive application with just few lines of code.

You can follow along by downloading the codebase for the demo application from  github.

$ git clone https://github.com/42id/ngFoodTruck.git

Stage 0 - Getting started.

Let's start with a basic Angular JS seeder project - one with a module, controller and view hooked in. Run the following command to get started with the base application:

$ git checkout -f stage0

Let's see what we have here and familiarize ourself with the structure. We have our index.html file that has the references to our JavaScript files and the Bootstrap CSS. The scripts/app.js file bootstraps our Angular application. We are using ui-router for managing our routes and we have one default route plugged in to get us started. We also define the controller and view associated with our default route in our app.js file. We use the 'Controller as' syntax with our home controller and we alias our controller as vm (short for view model).

app.js


(function () {
    'use strict';
    var app = angular.module('app', ['ui.router']);
    app.config(function($stateProvider, $urlRouterProvider) {
    $urlRouterProvider.otherwise('/home');
    $stateProvider
        // home <- starting state
        .state('home', {
            url: '/home',
            templateUrl: 'scripts/home/home.html',
            controller: 'home as vm'
        })
    ;
});
})();

Next up we have the definition for our partial view and our controller in the home directory.

Third party vendor specific JavaScript files are located under scripts/vendor folder and our bootstrap.css is located in the css folder

Let's go ahead and open up our index.html page in our favorite browser. We should see a message 'Hello from homeController init method' indicating that all plumbing is set up correctly and our application is functional.

Stage 1 - Setup the form elements and GeoLocation Service

Let's open up our home/home.html file and add the form elements for capturing the name of the food truck and the description/specials they offer. Let's also add a button for saving the data and stub out a save method in our controller.

homeController.js


(function(){
    'use strict';
    angular.module('app').controller('home', home);
    function home(geoLocationService){
        var vm =this;
        vm.includeLocation = true;
        vm.location = {};
        vm.message = "Hello from Home";
        init();
        function init(){
            vm.message = "Hello from homeController init method";
        }
        vm.save = function(){
            // Implement our save function to persist data to Firebase
        }
    }
})();

Next, we are going to hook into the geolocation capability of the browser.  Click here for an in depth documentation on HTML5 geolocation API. To do this, we first create a script/services.js file and add the following code:

services.js


(function(){
    'use strict';
    angular.module('app').factory('geoLocationService', ['$q', '$http', function($q, $http){
        var getLocation = function() {
            var defer = $q.defer();
            // If supported and have permission for location...
            if (navigator.geolocation) {
                // 
                navigator.geolocation.getCurrentPosition(function(position){
                    var result = {latitude : position.coords.latitude , longitude : position.coords.longitude}
                    // Adding randomization since we are all in the same location...
                    result.latitude += (Math.random() >0.5? -Math.random()/100 : Math.random()/100  );
                    result.longitude += (Math.random() >0.5? -Math.random()/100 : Math.random()/100  );
                    getNearbyCity(result.latitude, result.longitude).then(function(data){
                        result.address = data.data.results[1].formatted_address;
                        defer.resolve(result);
                    });
                }, function(error){
                    defer.reject({message: error.message, code:error.code});
                });
            }
            else {
                defer.reject({error: 'Geolocation not supported'});
            }
            return defer.promise;
        }
        var getNearbyCity = function (latitude, longitude){
            var defer = $q.defer();
            var url = 'http://maps.googleapis.com/maps/api/geocode/json?latlng=' + latitude +',' + longitude +'&sensor=true';
            $http({method: 'GET', url: url}).
                success(function(data, status, headers, config) {
                     defer.resolve({data : data});
                }).
                error(function(data, status, headers, config) {
                  defer.reject({error: 'City not found'});
                });
            return defer.promise;
        }
        var service = {
            getLocation : getLocation,
            getNearbyCity: getNearbyCity
        };
        return service;
    }]);
})();

Few points of interest here. We are injecting Angular JS's $q implementation into our services module. We wrap our geolocation call inside a $q promise keeping with the asynchronous nature of JavaScript. We expose our service implementation through a revealing pattern.

Next, we update our home controller to call this service and return the geolocation.

homeController.js


(function(){
    'use strict';
    angular.module('app').controller('home', home);
    function home(geoLocationService){
        var vm =this;
        vm.includeLocation = true;
        vm.location = {};
        vm.message = "Hello from Home";
        init();
        function init(){
            vm.message = "Hello from homeController init method";
            geoLocationService.getLocation().then(function(result){
                vm.location = result;
            });
        }
        vm.save = function(){
            // Implement our save function to persist data to Firebase
        }
    }
})();

We also add this newly created script/services.js file reference in our index.html. Finally update our home.html file to show render the geolocation coordinates

Running the following command will bring you up to this point.

$ git checkout -f stage1

Stage 2 - Persisting data with Firebase

In order to persist our data in Firebase, add reference to firebase.js and angular-fire.js libraries in index.html. Next, inject Firebase into app.js


(function () {
    'use strict';
    var app = angular.module('app', ['ui.router', 'firebase']);
    app.config(function($stateProvider, $urlRouterProvider) {
    $urlRouterProvider.otherwise('/home');
    $stateProvider
        // home <- starting state
        .state('home', {
            url: '/home',
            templateUrl: 'scripts/home/home.html',
            controller: 'home as vm'
        })
    ;
});
})();

Modify services.js file to add a new service that will interface with Firebase for us. Our controller will call methods in this service to retrieve and post data into Firebase.


 angular.module('app').factory('dataService', ['$firebase','$q', function($firebase,$q){
        var firebaseRef= new Firebase("https://popping-torch-4767.firebaseio.com/");
        var getFirebaseRoot = function(){
            return firebaseRef;
        };
        var getFoodTruckNode = function(){
            return getFirebaseRoot().child("FoodTrucks");   
        }
        var addData = function(data){
            // persist our data to firebase
            var ref = getFoodTruckNode();
            return  $firebase(ref).$push(data);
        };
        var getData = function(callback){
            var ref = getFoodTruckNode();
            return $firebase(ref).$asArray();
        }
        var service = {
            addData : addData,
            getData: getData,
            getFirebaseRoot: getFirebaseRoot            
        };
        return service;
    }]);

Next, update our homeController.js file to initially retrieve existing data for food trucks and fill in our save method to persist data in Firebase.


(function(){
    'use strict';
    angular.module('app').controller('home', home);
    function home(geoLocationService, dataService){
        var vm =this;
        vm.includeLocation = true;
        vm.location = {};
        vm.message = "Hello from Home";
        init();
        function init(){
            geoLocationService.getLocation().then(function(result){
                vm.location = result;
            });
            vm.message = dataService.getFirebaseRoot().toString();
            vm.foodTrucks = dataService.getData();
        }
        vm.save = function(){
            dataService.addData({name: vm.name, text: vm.text, address: vm.location.address});
        }
    }
})();

Running the following command will bring you up to this point.

$ git checkout -f stage2

Stage 3 - Adding directive to display map and markers

For the final stage, we are going to create a directive, that hooks into Google Maps API and a spatial query service from Firebase to display a map and plot all food trucks that are within 15 miles of our location.

In order to do this, we add references to Google Map API as well as GeoFire library - the spatial query service from Firebase

Next, we'll modify our services.js file to include calls to GeoFire and add our directives.js to display the map and the markers

services.js


angular.module('app').factory('dataService', ['$firebase','$q', function($firebase,$q){
    var firebaseRef= new Firebase("https://popping-torch-4767.firebaseio.com/");
    var geoFire = new GeoFire(firebaseRef.child("_geofire"));
    var getFirebaseRoot = function(){
        return firebaseRef;
    };
    var getGeoFireNode = function(){
        return geoFire;   
    }
    var getFoodTruckNode = function(){
        return getFirebaseRoot().child("FoodTrucks");   
    }
    var addData = function(data, locationData){
        // persist our data to firebase
        var ref = getFoodTruckNode();
        return  $firebase(ref).$push(data).then(function(childRef){
               addGeofireData({key: childRef.name(), latitude: locationData.latitude, longitude: locationData.longitude});
        });
    };
    var addGeofireData = function(data){
        var defer = $q.defer();  
        geoFire.set(data.key, [data.latitude, data.longitude]).then(function() {
            defer.resolve();
          }).catch(function(error) {
            defer.reject(error);
        });
        return defer.promise;
    };
    var getData = function(callback){
        var ref = getFoodTruckNode();
        return $firebase(ref).$asArray();
    }
    var service = {
        addData : addData,
        getData: getData,
        getFirebaseRoot: getFirebaseRoot,
        getGeoFireNode : getGeoFireNode
    };
    return service;
}]);

directives.js


(function(){
angular.module('app').controller('mapDirectiveController', ['$scope', 'dataService', function($scope, dataService){
        var self = this;
        self.map;
        self.setMarker = function(markerLocation){
            var location = markerLocation.location;
            var key = markerLocation.key;
             if (location.length==0)
                 return;
            var marker = new google.maps.Marker({
                icon: 'https://maps.google.com/mapfiles/ms/icons/green-dot.png',
                position: new google.maps.LatLng(location[0],location[1]),
                optimized: true,
                map: self.map
            });   
        }
        function initializeMap(){
            if ($scope.vm.location.latitude ==undefined)
                return;
            self.map = new google.maps.Map(self.$element[0], {
                center:new google.maps.LatLng($scope.vm.location.latitude ,$scope.vm.location.longitude),
                zoom: 13,
                mapTypeId: google.maps.MapTypeId.ROADMAP
            });
          var geoQuery = dataService.getGeoFireNode().query({
                    center: [$scope.vm.location.latitude ,$scope.vm.location.longitude],
                    radius: 15
                });
            geoQuery.on("key_entered", function(key, location) {
                var item = {key:key, location:location};
                console.log('key entered on directive controller ' + key);
                self.setMarker(item);
            });  
        }
        self.init = function( element ) {
            self.$element = element;
            //only initialize map when latitude and longitude are available
            $scope.$watch(function(){return $scope.vm.location.latitude}, initializeMap);
          };
    }]);
angular.module('app').directive('mapDirective', mapDirective);
    function mapDirective(){
        return {
            restrict: 'E',
            controller:'mapDirectiveController',
            template: '<div style="height: 400px; width:400px;">This is from directive</div>',
            link: function (scope, elem, attrs, ctrl){
                ctrl.init(elem);
		     },
            replace: true
        }
    }    
})();

Running the following command will bring you up to this point.

$ git checkout -f stage3

Check out the live demo.

You may also be interested in using Firebase with Grooveshark to be your office DJ + a fun chat feature that likes Giphy URLs. You can check it out here

Was this useful?

Have questions? We have answers. Contact us, we are glad to help!

Spread the word if you found this useful! Tweet us at @thelatestbrian and @yanamegainfo

What will you learn next?

JavaScript AngularJS Firebase Neo4j Responsive Design

Receive the latest fluff-free, no-BS videos, tutorials, and articles on web technologies for the working developer.

We won't send you spam. Unsubscribe at any time.