I’ve been working on an AngularJS app backended by Firebase – both building some experiments at work and also incorporating the stacks in a couple of side projects. Firebase has a fairly interesting Angular library they call AngularFire, which offers a socket.io-like feature to easily sync your data between multiple clients and the server (you can read up on it a bit here and here). When paired with Angular, this allows for a “three-way data bind”, which is basically a riff off of Angular’s two-way data bind, between your model and view and expands it to your server.
Firebase has some pretty elaborate fully built apps, but as a proof of concept in how to use what I saw as the core features I put together an active users list – think of it like what you get in your Facebook sidebar. The currently logged in users get displayed, and the fact that a new user has arrived is broadcasted to the other currently logged in users. And as a bonus, I include a few extra design patterns so that you don’t have to do a lot of guess work on how to stitch it all together.
#1 – Setup your Angular application
So I will make a basic assumption that you can setup an authentication method for your application. For the sake of simplicity I will use the design pattern suggested here.
So use your Auth method, and get your user logged in. The next thing to do is setup Firebase as part of your application. You will need to look up the most recent versions of both Firebase and AngularFire and add them to your main application HTML file. I have those listed below as of this writing.
1 2 |
<script src="https://cdn.firebase.com/js/client/2.1.0/firebase.js"></script> <script src="https://cdn.firebase.com/libs/angularfire/0.9.1/angularfire.min.js"></script> |
Next, you need to add Firebase to your Angular build. I also like to add my Firebase location as a constant so I don’t have to keep referring to the entire string. And that’s really all you need to have Firebase wired up.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Angular .module('AngularFireExample', [ 'firebase' ]) .constant('KEYS', { firebase: 'https://your_location.firebaseio.com/', }) .run(['$rootScope', 'reBuilder', 'Session', function($rootScope, reBuilder, Session) { $rootScope.$on('$routeChangeSuccess', function(event, next, current){ if(next.locals.currentAuth != null && angular.isUndefined(Session.id)) { return reBuilder.andGo(next.locals.currentAuth.uid); } }); }]); |
The next thing I have done is add a check inside of .run (for more information you can check out the Modules spec and find Run Blocks under “Module Loading & Dependencies”). What this does:
- When the route has changed (using $routeChangeSuccess found here) ,
- I look for currentAuth (using the Firebase Auth design pattern).
- If currentAuth is empty and my Session variable is empty (which I have not yet introduced),
- I rebuild the Auth and the Session
This process is aimed not at keeping out unauthenticated users (again, I am assuming you can build that system for your application), but for handling events like reloads and revisits without a) requiring the user to log in every time and b) rebuilding your entire user profile every time the user changes the view.
So let’s look at some of the guts of Session and Rebuild, which then lead on to the more interesting Firebase work.
#2 – Create some factories to handle some common work
I use a factory I call reBuilder to recreate any lost information from the last time the user was on the page. The user may still be authenticated to Firebase, but the client may need profile information or the index key of the user. In this example, Auth is a factory that uses Firebase’s recommended method. ActiveUsers will be addressed in the next section.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
.factory('reBuilder', ['$q', 'Session', 'ActiveUsers', 'Auth', function($q, Session, ActiveUsers, Auth) { return { andGo: function(id) { var defer = $q.defer(); if(!id) { var currAuth = Auth.$getAuth(); id = currAuth.uid; } Session.FindKeyRevisit(id).then(function(resp) { Session.create(id, resp.$value); ActiveUsers.tokenMagic(); defer.resolve(); }); return defer.promise; } }; }]) |
This leads us to a Session service. I use a basic service to store some common variables. In this example, I only create Session, but in a fuller version, you would destroy, edit, etc. I’m also only storing the userId and a key in this example, but you could clearly store as much or as little as you like there. Do keep in mind, though, that this is a client-side app and a variable called Session would probably stand out to anyone sniffing around for sensitive data. Just saying.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
.service('Session', ['$q', '$firebase', 'KEYS', function($q, $firebase, KEYS) { this.create = function (userId, key) { this.id = userId; this.key = key; }; this.SetKey = function (id, key) { var oref = new Firebase(KEYS.firebase).child('place_of_user_data').child(id).child('key'); var obj = $firebase(oref).$asObject(); obj.key = key; obj.$save().then(function(ref) { }, function(error) { console.log('Error:', error); }); this.key = key; }; this.FindKeyRevisit = function (id) { var promise = $q.defer(); var oref = new Firebase(KEYS.firebase).child('place_of_user_data').child(id).child('keys').child('key'); var obj = $firebase(oref).$asObject(); obj.$loaded().then(function(data) { promise.resolve(obj); }); return promise.promise; }; return this; }]) |
#3 – Use Firebase to create keys for associative records
In ActiveUsers, I have created a function that uses Firebase to generate and track user keys. It makes use of a number of AngularFire functions, so first the code, then some comments.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
.factory('ActiveUsers', ['$firebase', 'ActiveUserList', 'Session', 'KEYS', function($firebase, ActiveUserList, Session, KEYS) { return { tokenMagic: function() { var userRef = new Firebase(KEYS.firebase).child('activeusers'); var theList = $firebase(userRef).$asArray(); theList.$loaded(function (list) { if(Session.key === null || list.$indexFor(Session.key) === -1) { list.$add({1: Session.id}).then(function(ref) { Session.SetKey(Session.id, ref.key()); }); } else { } }); var self = []; self.ActiveUsers = {}; theList.$watch(function(event) { if(event.event === "child_removed") { delete self.ActiveUsers[event.key]; ActiveUserList.prepForBroadcast(self.ActiveUsers); } else { var userRef = new Firebase(KEYS.firebase).child('activeusers').child(event.key).child('1'); var userObj = $firebase(userRef).$asObject(); userObj.$loaded( function(data) { self.ActiveUsers[event.key] = { name: data.$value }; ActiveUserList.prepForBroadcast(self.ActiveUsers); }); } }); } }; }]) |
The design pattern I’m aiming for looks something like the below.
Queries on Firebase can be a little funky, and while they are getting better, they are still a bit of a mental loop. Essentially I am using AngularFire’s $asArray, which includes an $indexFor option. If the index does not exist, it can return -1. I am basically checking to see if the keys are in sync in both positions. If they happen to be out of sync, new keys will be written for this user.
Finally, I am using $watch to sync the changes that occur with activeusers to my model and my view. When changes do happen, the list gets updated across all clients, and those changes are broadcasted by a factory to a controller that is listening for them.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
.factory('ActiveUserList', ['$rootScope', function($rootScope) { var ActiveUserList = {}; ActiveUserList.prepForBroadcast = function(msg) { var self = this; self.list = msg; self.broadcastItem(); }; ActiveUserList.broadcastItem = function() { $rootScope.$broadcast('handleBroadcast'); }; return ActiveUserList; }]) |
And then in your controller…
1 2 3 4 5 6 7 8 9 |
.controller('ListCtrl', ['$scope', 'ActiveUserList', function ($scope, ActiveUserList) { var self = this; self.ActiveUsers = {}; $scope.$on('handleBroadcast', function() { if(ActiveUserList.list) { self.ActiveUsers = ActiveUserList.list; } }); }]); |
And finally your view…
1 2 3 4 5 |
<ul> <li ng-repeat="people in theview.ActiveUsers"> {{people.name}} </li> </ul> |
#4 – Consider clean up options for your build
I am not going to include clean up options because those are going to be really specific to the needs of your particular project. Facebook considers the user logged off when the user closes the window, so you may want to consider onbeforeload (spec here) to do some actions to change your user’s state. Time stamps are another way to go. You will also want to consider how to clean up unused keys for when your users log back in and update them.
There are several design decisions that go into all of that but with a means of getting things started, hopefully making the back piece of the work finish out will be a few steps easier.
Feel free to leave any questions or comments below!
It was exactly what i was looking for, thanks so much for sharing with people…