##Objective Use Firebase and Angular to persist data into a web application that acts like a real-time forum
In this project, you'll get comfortable with hooking Firebase to your Angular application in order to persist your data.
We're going to create a multi-user, real-time forum (RTFM).

- Create the basic structure of your Angular application naming your app 'rtfmApp'.
- After you include Angular, include firebase, angularfire, and ui-router as scripts in your html file (Google them), then the rest of your basic angular files.
- In your app.js file include
firebaseandui.routerto your module's dependencies. - Add a
.configfunction and include$stateProviderand$urlRouterProviderto your injections. - Create a router and add
/threadsand/threads/:threadIdas the URLS. - Use
.$urlRouterProvider.otherwiseto redirect any other url to/threads. - In your index.html file, include your ui-view attribute/element in order to tie in your router. Should look like this below.
<div class="container" ui-view></div>Note: In today's project we'll ignore authentication, but tomorrow we'll make it so users need to log in to see certain routes/data and we'll persist that user state with Firebase. But for now, we'll just set up the basic structure for that in order to build on top of this functionality tomorrow.
Firebase is very dependent upon URLs, meaning, if you want to set data, get data, remove data, etc, you'll do that based on a Firebase URL. Because of this, it's important that we're able to access our Firebase URL from anywhere. To accomplish this, we'll add a constant to our Angular app. A constant is a very common thing in Software Development. It allows us to set a value that won't change.
- Head over to your
app.jsfile or wherever you're initiating your new app and right above your.configmethod, add a.constantmethod with the first argument being "fb" (which is the name of the constant) and the second argument being an object with a key of "url" whose value is "https://[your-firebase-app-name-here].firebaseio.com/".
You can think of this fb constant as any other service. We're now able to inject fb anywhere we need it and grab the url property off of it in order to get our Firebase URL.
Go and create your own Firebase project so you can see and handle the data yourself. To do so, head to Firebase.com and sign up. Once you do that you'll have the option to create a new project. Once you do that, copy the URL it gives you and use your Firebase app's URL with the constant call.
.constant('fb', {
url: 'https://[your-firebase-app-name-here].firebaseio.com/'
});Now we need to actually create our Threads view and controller.
- Create a
threads.htmlview and athreadsCtrl.jscontroller in the appropriate folders. Add the new view and controller to the/threadsroute inapp.js.
-
Create a threadService and put it in the appropriate folder.
-
On that threadService create methods (on
this) namedgetThreadsandgetThread. -
Inject your
fbconstant to get your Firebase url.
In order to create a reference to your Firebase (which will allow us to get, delete, add, and update data in our firebase database), we'll need to create a new instance of Firebase passing in the URL to our app.
For example, if my base url was "https://my-cool-app.firebaseio.com/" then I would do
var firebaseRef = new Firebase("https://my-cool-app.firebaseio.com/"); to create that reference. Now on firebaseRef, I can do a bunch of fancy things in order to manipulate my data that lives on my Firebase.
- Inside of your
getThreadsmethod, return a new instance of Firebase passing in your base url you get from thefbconstants service you set up earlier + '/threads'.
That method should look like this
this.getThreads = function(){
return new Firebase(fb.url + '/threads');
}- Now, have the other method (
getThread) take in athreadIdas its only parameter and return a new instance of Firebase passing in base URL +/threads/+threadId.
Now that your threadService is set up, we're going to use resolve in our routes in order to make sure the data in our Firebase is ready for us when our controller loads.
- Head over to your
app.jsfile and in the/threadsroute, add a resolve property on the object whose value is another object which has a property of theadsRef whose value is a function. That function is going to take in thethreadServicewe just built and it's going to returnthreadService.getThreads().
Now since we're using resolve, threadsRef will be available in our controller if we inject it in and its value will be the data which is coming from our getThreads() method.
-
Open up your
threadsCtrl.jsAdd pass inthreadsRefto the threadsCtrl controller, as well as$firebaseArray. -
Set a property on the $scope object called
threadswhich is set to$firebaseArray(threadsRef).
Remember, threadsRef is the result of calling getThreads which just returns us new Firebase('THE FIREBASE URL' + /thread) and $firebaseArray just makes it so it gives our data back to us as an Array.
angular.module('rtfmApp')
.controller('threadsCtrl', function ($scope, threadsRef, $firebaseArray) {
$scope.threads = $firebaseArray(threadsRef)
});- Let's set up
threads.htmlwith a list of threads, an input and a button to create a new thread, and links to each thread's unique page.
<div>
<p>Threads</p>
<form name="newThreadForm">
<input type="text" ng-model="newThreadTitle" placeholder="New thread title..." required/>
<input type="text" ng-model="username" placeholder="Username..." required/>
<button ng-disabled="newThreadForm.$invalid" ng-click="createThread(username, newThreadTitle)">Add Thread</button>
</form>
<ul>
<li ng-repeat="thread in threads">
<a ui-sref="thread({threadId: thread.$id})">
<span>{{ thread.title }}</span>
<span>(by {{ thread.username }})</span>
</a>
</li>
</ul>
</div>- You'll need to create a function in your
threadsCtrlnamedcreateThread. This function must be attached to$scopeand should accept a username and a thread title as arguments. It will then use the AngularFire "array"$addfunction to add the new thread to thethreadsarray. Once you get this working, you'll be able to add threads in your view and watch them automatically add themselves to the threads list.
angular.module('rtfmApp')
.controller('threadsCtrl', function ($scope, threadsRef) {
$scope.threads = $firebaseArray(threadsRef)
$scope.threads.$loaded().then(function (threads) {
console.log(threads);
});
$scope.createThread = function (username, title) {
$scope.threads.$add({
username: username,
title: title
});
};
});If you've done everything correctly, you should be able to load your page using a local server (e.g. http-server) and see the list of threads. Try to add some from your Firebase app's web console (open the URL in your browser), or add some from your app directly with the HTML provided.
- Create a
threadCtrland athread.html - Add the new controller and view to the
/threads/:threadIdroute inapp.js. Also create a resolve forthreadthat uses$stateParams.threadIdandthreadService.getThread()to inject each thread's AngularFire ref into your newthreadCtrl.
.state('thread', {
url: '/threads/:threadId',
templateUrl: 'path/to/thread.html',
controller: 'threadCtrl',
resolve: {
threadRef: function (threadService, $stateParams) {
return threadService.getThread($stateParams.threadId);
}
}
});- Inject
threadRefinto yourthreadCtrland use AngularFire's$firebaseObjectand$bindTomethods to bind the thread to$scope.thread.
angular.module('rtfmApp')
.controller('threadCtrl', function ($scope, threadRef, $firebaseObject) {
var thread = $firebaseObject(threadRef);
thread.$bindTo($scope, 'thread');
});Why $firebaseObject and $bindTo???
AngularFire refs can get converted into AngularFire "objects". These "objects" can be bound to $scope using
AngularFire's $bindTo function. This sets up 3-way binding from your view, through $scope and all the way back to your Firebase data store. You can edit these AngularFire "objects" in place in your view and watch the changes propagate throughout your entire app.
- Edit your thread template to create inputs to add comments under the thread as well as read out all existing comments.
<div>
<h1>{{ thread.title }} (by {{ thread.username }})</h1>
<form name="newCommentForm">
<input type="text" ng-model="newCommentText" placeholder="Write a comment..." required/>
<input type="text" ng-model="username" placeholder="Username..." required/>
<button ng-disabled="newCommentForm.$invalid" ng-click="createComment(username, newCommentText)">Add Comment</button>
</form>
<ul>
<li ng-repeat="comment in comments">{{ comment.username }}: {{ comment.text }}</li>
</ul>
</div>Notice how we're looping through comment in comments? We're going to want each thread to have an "array" of
comments in its Firebase data structure. We haven't created the comments "array" yet, but we can create an
AngularFire ref to it anyway. Firebase will treat that ref as if it already exists, so we can loop through it and add to it seamlessly. This will require creating a new getComments method in threadService and injecting this new commentsRef into threadCtrl using a resolve in your thread route.
This may seem like a lot of steps, but you've already gone through these steps twice with threadsRef and
threadRef. The new commentsRef follows the same pattern.
- In your
threadServicecreate a getComments method which takes in athreadIdand returns a new Firebase instance passing in the base url + '/threads/' + threadId + '/comments'.
this.getComments = function (threadId) {
return new Firebase(fb.url + '/threads/' + threadId + '/comments');
}- In your
app.jsfile under your/threads/:threadIdroute under resolve, add acommentsRefkey whose function takes inthreadServiceas well as$stateParamsand return the invocation ofthreadService.getComments($stateParams.threadId).
It should look like this,
commentsRef: function (threadService, $stateParams) {
return threadService.getComments($stateParams.threadId);
}-
Now in your
threadCtrlinjectcommentsRefas well as$firebaseArrayand on the $scope object set acommentsproperty equal to the invocation of $firebaseArray passing incommentsRef. -
Now add your
createCommentmethod to the $scope object. This method should take in a username and a text and then invoke the $add property on$scope.commentspassing it an object with a key of username and the value being the username you passed in as well as a key of text and a value being the text you passed in. The finalthreadCtrlshould look like this,
.controller('threadCtrl', function ($scope, threadRef, commentsRef, $firebaseObject, $firebaseArray) {
var thread = $firebaseObject(threadRef);
thread.$bindTo($scope, 'thread');
$scope.comments = $firebaseArray(commentsRef);
$scope.createComment = function (username, text) {
$scope.comments.$add({
username: username,
text: text
});
};
});Notice that we've added a new $scope.createComment function. This will get called from the thread.html view and adds a comment to your AngularFire comments "array".
At this point, you should be able to see a list of threads on the main /threads route. You should also be able to add a new thread, as well as click on any of the threads to drill down and see and add comments. All in real-time!
We're going to add Firebase's authentication to our app. Buckle up.
You need to go to your Firebase app and enable authentication. For this app we're going to enable email/password authentication. Head over to your Firebase dashboard and configure the email/password authentication piece.
Once that's finished, add these additional routes to your app:
State: login Url: '/login' controller: 'loginCtrl' templateUrl: 'login.html'
State: signup Url: '/signup' controller: 'signupCtrl' templateUrl: 'signup.html'
We're going to utilize a service to manage our authentication for us. Create a userService and add the following methods:
getUser(returns the$getAuth()result)register(returns the$createUser(newUser)result)login(returns theauthWithPassword(user)result)
Above your service methods, create an authRef (using Firebase) and an auth object (using $firebaseAuth). This should look something like this:
var authRef = new Firebase(firebaseUrl.url);
var auth = $firebaseAuth(authRef);In your signup controller, create a $scope.register method that calls the userService's register method. You'll need to pass in the new user object (including email and password) into this method. Be sure you have the inputs and ng-models necessary in your view to accomplish this.
In the login controller, create a $scope.login method that calls the userService's login method. You'll need to pass in the user object (including email and password) from the view into this method. Be sure you have the inputs and ng-models necessary in your view to accomplish this. If the login succeeds, call $state.go to redirect the user to the threads page.
We're going to use a nifty ability in routing to create a logout app.
.state('logout', {
url: '/logout',
controller: function(UserService) {
return userService.logout();
},
})See what we're doing? The only purpose of this route state is to call the userService.logout method.
this.logout = function(user) {
return auth.$unauth();
}You're also going to want to watch the $onAuth in the userService so we can send the user to the correct view when they're not logged in.
auth.$onAuth(function(authData) {
if (!authData) {
$state.go('login')
}
});If you want another challenge, create a "logout" directive that checks the userService for the user's auth status and shows a Log out link if appropriate. This directive could be placed anywhere in your app and would allow the user to see their current state and log out.
If you see a problem or a typo, please fork, make the necessary changes, and create a pull request so we can review your changes and merge them into the master repo and branch.
© DevMountain LLC, 2017. Unauthorized use and/or duplication of this material without express and written permission from DevMountain, LLC is strictly prohibited. Excerpts and links may be used, provided that full and clear credit is given to DevMountain with appropriate and specific direction to the original content.