Introduction

There are a myriad of videos & blog posts available on the web that explain the different concepts that comprise AngularJS. However, there are few tutorials that actually show you how to manipulate these concepts for the purpose of building slick single-page apps. In this tutorial, we will show you how to build an entire production ready application with AngularJS.

Our intention is to provide the AngularJS community with instructions on how to use AngularJS correctly and effectively, but also in its most modern form. The application you are building will go beyond basic use of AngularJS, and we will attempt to explore as much of the framework as possible. We also feel strongly about maintaining modernity in a tutorial, so we will keep it congruent with AngularJS as the framework and community matures. This tutorial is currently built on top of AngularJS v1.2.25.

The tutorial is a living thing as we are constantly extending the tutorial and making changes and corrections. If you find errata, think something should be changed, or would like to suggest an improvement or new section, we would love to hear from you!

What You Will Build and Learn

We have chosen an application that nearly everyone can relate to: a social link sharing site called "ang-news" that shares similar functionality with Reddit, Digg, and Hacker News. This application is not unwieldy yet it utilizes nearly all major concepts in AngularJS.

For those unfamiliar with social link sharing sites, the general premise is that users will come to your site to share interesting links. If other users think a link is interesting, they will "upvote" it. If enough users upvote a link in a certain period of time, it will reach the front page where a mass of other users can read it and/or upvote it. If you're still not sure how it works, here is a superb explanation that explains how reddit works

By building ang-news, you will learn how to develop CRUD (create, read, update, delete) services, securely authenticate users, access servers with $http, communicate with REST APIs using $resource, enable three-way data binding with your server, create directives for your user interface and more.

Once you have finished building the ang-news application, your users will be able to:

  • Sign up for an account on your site
  • Post links to the site, as well as delete their own links
  • Comment on links
  • Have a user profile with a picture and a list of user's posts

You can view a working demo of the final link sharing application here. (We've disabled registration on the demo to avoid spam)

Prerequisites

This tutorial assumes you are familiar with the various concepts in AngularJS. Throughout, there will be references to parts of the A Better Way to Learn AngularJS curriculum if you need clarification or refreshing on a certain subject. We recommend going through the entire curriculum before beginning this tutorial, but you can also pick up these concepts along the way.

The Stack

AngularJS is not tied to any specific backend server language or framework, thus this tutorial will not require you to install any server-side software on your computer. We've found that setting up different backends is prone to odd errors when dealing with different operating systems and software versions which can distract from learning the topic at hand - AngularJS.

Instead, we have opted to have you use a cloud based REST API that can also support real-time connections called Firebase. We'll automatically generate a Firebase server for you to use and it will ensure that you don't run into any errors when setting your backend up in the tutorial. We will teach you how to interact with the Firebase server REST-fully using $resource and $http, just like you would with any other backend. We'll also show you how to enable real-time features in your application using websockets. The way you will interact with the Firebase server is not dramatically different than any other server, so swapping out Firebase for your backend of choice is not difficult. In the near future, we will release a series of posts thoroughly explaining how to merge popular frameworks like Rails and Django with AngularJS.

Final Notes About the Tutorial

You should never copy and paste code from this text unless we tell you to, as we've found that the skills being taught will stick better if you write out all of the code yourself. If you need more clarification on a certain part of the tutorial, we recommend that you download the supplementary screencast series as we go into far more detail in each of the videos. It's also significantly easier to learn from the screencast series than the ebook, as you can literally see how a skilled developer manipulates the concepts in AngularJS to build a working application. If you need more help learning, our friends at bloc.io can give you continuous 1-on-1 coaching from experienced developer mentors. They have also generously offered $100 off to the users of Thinkster; just type in "thinkster" as the offer code and you should be good to go.

Getting Your Project Set Up with Yeoman

For those of you who have been developing on the web for a while, you know the hassle of starting a new front-end project. This usually involves setting up various JavaScript/CSS frameworks & libraries, scaffolding your base application and keeping your frameworks and libraries up to date by hand.

You no longer have to do this all by hand, as there are three tools now available to solve these problems:

  • Yeoman, which scaffolds out your AngularJS applications, writing your Grunt configuration and pulling in relevant Grunt tasks and Bower dependencies that you might need for your build.
  • Grunt, which is used to build, preview and test your project. It can run a local server that automatically refreshes the page when you edit any HTML/CSS/JS (no hitting the refresh button for every code change)!
  • Bower, which is used for dependency management, so that you no longer have to manually download and manage your scripts.

Utilizing these three tools will make your life easier in both the short term and the long term. It makes scaffolding your applications incredibly easy but it will also save you time down the road when your AngularJS application depends on thirty libraries that need to be updated. Yeoman, Grunt and Bower are simple to use and we recommend that you embrace them instead of defaulting to your normal way of building web apps.

You'll need npm installed on your computer in order to use these tools, so make sure Node.JS and npm are fully installed before proceeding (instructions).

Installing Yeoman

To install Yeoman, simply enter this into your terminal:

npm install -g yo

Once yeoman has finished installing, we then need to install the AngularJS scaffolding tool for Yeoman:

npm install -g generator-angular

Finally, you'll need to make sure both bower and grunt are available on the global path:

npm install -g bower grunt

You can now start scaffolding your apps with Yeoman, managing dependencies with Bower, and building & running your application with Grunt! Lets start by scaffolding our ang-news AngularJS application.

Scaffolding Your App

Create a folder on your computer called 'ang-news'. In your terminal, navigate to that folder and then run the following command:
yo angular

This command scaffolds an AngularJS application in the current folder. After running yo angular, Yeoman will ask you a few questions regarding how you want to set up your application. Below are a list of the questions and their respective answers for this tutorial.

? Would you like to use Sass (with Compass)? (Y/n)

Answer 'n'. Sass is a great CSS framework and we recommend you use it. However, we aren't teaching you Sass/CSS in this tutorial and it can be a hassle to install (as it depends on Ruby and Bundler to be installed).

? Would you like to include Bootstrap? (Y/n)

Answer 'y'. For this tutorial, we are going to use the base CSS & HTML from Twitter Bootstrap, the front-end framework from Twitter. Yeoman will automatically include the latest version of Twitter Bootstrap in our project and create a demo page using Bootstrap's styling.

? Which modules would you like to include? (Press <space> to select)

Hit 'enter'. These are optional AngularJS modules but we will be relying on some of them. In general, we recommending including them by default. You can learn more about what these modules do on this page in the AngularJS documentation under 'Additional AngularJS Modules'.

After hitting enter, Yeoman will get to work scaffolding your AngularJS application (it will take a few seconds to complete). Once it has finished, your ang-news folder should look like this:

  • app (the guts of your AngularJS app)
    • images
    • scripts
      • controllers
        • main.js
        • about.js
      • app.js
    • styles
      • main.css
    • views
      • main.html
      • about.html
    • 404.html
    • index.html
    • favicon.ico
    • robots.txt
  • node_modules: contains node modules required for grunt, karma, etc - ignore for now
  • bower_components: contains frontend JavaScript/CSS libraries
  • test: used for testing your code, ignore for now
  • bower.json: where you define dependencies on other frameworks & libraries
  • Gruntfile.js: tells Grunt how to work with our project - ignore it
  • packages.json: used for node dependencies like grunt karma, etc - ignore

Getting Familiar with Your Application

The app folder is where you'll be spending most of your time, as that's where you will be writing your JavaScript, HTML and CSS for the ng-news app. Lets explore the different files in /app and see how they're connected to each other.

The app/index.html file is where your application starts: it loads the JavaScript & CSS files you write (from app/scripts and app/styles), the JavaScript & CSS files that your application depends on (in bower_components), and sets up HTML elements for your AngularJS app to bind to.

Open up app/index.html in your code editor and look over its structure. Notice that we're attaching our AngularJS application to the body element <body ng-app="angNewsApp"> and all of our views are being rendered in a container div:

<div class="container">
  ...
  <ng-view=""></div>
</div>

It's also worth pointing out that Yeoman named our AngularJS application 'angNewsApp' after the folder it resides in (ang-news).

Right before the </body> tag, there are a handful of JavaScript files being included. They are split into two sections: one for your application's dependencies (managed via Bower) and one for your application's JavaScript files. It should look something like this:

<!-- build:js(.) scripts/vendor.js -->
<!-- bower:js -->
<script src="bower_components/jquery/dist/jquery.js"></script>
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/bootstrap/dist/js/bootstrap.js"></script>
<script src="bower_components/angular-resource/angular-resource.js"></script>
<script src="bower_components/angular-cookies/angular-cookies.js"></script>
<script src="bower_components/angular-sanitize/angular-sanitize.js"></script>
<script src="bower_components/angular-animate/angular-animate.js"></script>
<script src="bower_components/angular-touch/angular-touch.js"></script>
<script src="bower_components/angular-route/angular-route.js"></script>
<!-- endbower -->
<!-- endbuild -->

    <!-- build:js({.tmp,app}) scripts/scripts.js -->
    <script src="scripts/app.js"></script>
    <script src="scripts/controllers/main.js"></script>
    <script src="scripts/controllers/about.js"></script>
    <!-- endbuild -->

The first section (starting with <!-- bower:js --> and ending with <!-- endbower -->) is where Bower automatically places all of your dependencies. Don't link to any JavaScript files in here as Bower will delete anything in there that is not listed as a dependency. Your files should go in the second section, in between <!-- build:js({.tmp,app}) scripts/scripts.js --> and <!-- endbuild -->. For every JavaScript file you create in your app/scripts directory, you will need to manually include it in this second section.

There are currently two scripts in this second section: scripts/app.js and scripts/controllers/main.js. Lets look at the code in app.js first. Yeoman created a module for our app called 'angNewsApp' which our index.html file references with <body ng-app="angNewsApp">. app.js also has a route set up for the root URL path '/' that points to a template at app/views/main.html with a controller named MainCtrl. If you look at MainCtrl in scripts/controllers/main.js, you'll see that the only code in there is an array of 'awesomeThings'. If you open up views/main.html, you should just see some demo HTML that Yeoman generated. There is nothing interesting in the MainCtrl or the MainCtrl's view - just example code that Yeoman spit out for us.

Lets see all of this action in the browser. Go to your terminal, navigate to your ang-news folder and then type grunt serve. This command starts a server on port 9000 that will serve the assets in your app folder. If you navigate to http://localhost:9000 you should see the HTML from app/views/main.html. Lets quickly review how this happened:

  1. We requested the index.html file from our Grunt server

  2. Grunt returned index.html, which told our browser to download our JavaScript & css files. index.html also set up the ng-app and ng-view elements for our AngularJS app to bind to.

  3. Once all of our JavaScript files were downloaded, our app.js file attached itself to our <body> tag and set our ng-view on the container div. It also set up a route for '/' that pointed to a view in views/main.html and a controller named 'MainCtrl'.

  4. Our app then looked at the URL and figured out what our current route was ('/'). It then included the views/main.html and injected MainCtrl as the controller, per the route configuration.

Lets see how MainCtrl and main.html work together. In main.html, delete all of the code and replace it with {{ awesomeThings }}. After saving, your browser should automatically refresh and show ["HTML5 Boilerplate","AngularJS","Karma"]. As you recall, this is the array from our MainCtrl in main.js. Feel free to add elements to the array and watch the page update - pretty neat, right?

At this point you should have a good understanding of how our application is set up. In the next chapter we will begin writing the code for our ang-news app.

Creating Our First Controller

Let's start by deleting unnecessary files that Yeoman generated for us. We won't need these anymore as we'll be writing our own views and controllers.

  • app/views/main.html
  • app/views/about.html
  • app/scripts/controllers/main.js
  • app/scripts/controllers/about.js

You can also remove any references to these files from index.html.

Configuring Your Environment

As we add new controllers to our AngularJS app, there's a very popular shorthand to use instead of having to write out angular.module('angNewsApp').controller(). By creating a global variable named 'app' that is set to angular.module('angNewsApp'), we can create new controllers with just app.controller(). Also, if we ever change the name of our application module from angNewsApp, we only have to do it once instead of on every controller!

In 'scripts/app.js', set a variable named 'app' to equal our angular.module(). Then, attach config() to our new app variable.
var app = angular
  .module('angNewsApp', [
    'ngAnimate',
    'ngCookies',
    'ngResource',
    'ngRoute',
    'ngSanitize',
    'ngTouch'
  ])
  .config(function ($routeProvider) {
If you look at your grunt terminal, you can see that there's a task called jshint that is run every time you change a JavaScript file. This checks your JavaScript for any syntax errors and gives you hints for fixing them. Ideally we want to have no errors in our application. In .jshintrc, we can add "app": false to the "globals" hash which tells jshint about app so we can use it in all of our files without any warnings. At the top of app.js, add
/* global app:true */
/* exported app */

This will let jshint know that app is defined in that file.

Creating the Posts Page

The fundamental feature behind ang-news is the ability to share links. We'll call these 'posts' from here on out, as users 'post' links to the site. Lets set up a page where we can post links as well as see all of the other links that have been posted. We will need to create a controller and a view to do this.

Create two new files - one in 'app/views/' called 'posts.html' and one in 'app/scripts/controllers/' called 'posts.js'.Open posts.html and place the infamous 'hello world' text in there. We'll put actual code in here momentarily but for now we just need something to display in the web browser.
<p>{{ 'hello ' + 'world!'}}</p>
Open up posts.js and initialize your controller for posts. We will call it 'PostsCtrl'.
'use strict';

app.controller('PostsCtrl', function ($scope) {

});

We now have a controller for our Posts page and we have a view that says 'hello world!'. Before our Posts page can be accessed by a web browser, we need to do two things:

  1. Create a new route in $routeProvider that points to our view's file and injects the controller.
  2. Include the controller's JavaScript file in our app/index.html file.
Lets set up the route for our posts page. Open up 'scripts/app.js' and change the root route '/' to point at our posts.html view and inject our 'PostsCtrl'.
.config(function ($routeProvider) {
  $routeProvider
    .when('/', {
      templateUrl: 'views/posts.html',
      controller: 'PostsCtrl'
    })
Now open up 'app/index.html' and remove the (now deleted) main.js file and replace it with our posts.js file.
<!-- build:js({.tmp,app}) scripts/scripts.js -->
<script src="scripts/app.js"></script>
<script src="scripts/controllers/posts.js"></script>
<!-- endbuild -->

When you open up your web browser, you should now see 'hello world!'. This means that our controller is being injected properly and the view is being rendered. We can now move on and start building the functionality behind posts.

Adding and Removing Posts

As you recall, a post consists of two things: a URL and a title. In our controller, lets create a scope object named 'post' that will contain these two attributes.
app.controller('PostsCtrl', function ($scope) {
  $scope.post = {url: 'http://', title: ''};
});
Our users will create new posts through a form in our view. Open views/posts.html and create a new form with two inputs that are bound to the post model as well as a submit button. (Also, be sure to remove our 'hello world!' text from before).
<form>
  <input type="text" ng-model="post.title" />
  <input type="text" ng-model="post.url" />
  <input type="submit" value="Add Post" />
</form>

You can see that our URL field as automatically been filled in with the "http://" from our model - pretty neat. Binding to a model is a very common way to pre-populate form inputs.

We need a way to save the new URLs that have been entered into our form. Therefore, we need a way to store our URLs. For our purposes, an array that holds our posts should suffice.

In our controller, create a new array called 'posts'.
app.controller('PostsCtrl', function ($scope) {
  $scope.posts = [];
  $scope.post = {url: 'http://', title: ''};
  });
Now that we have a place to store our posts, we need to actually insert new posts into this array. Lets create a method on the scope that will add the 'post' object into the 'posts' array. We also need to reset the post object when this has happened, as we don't want that data in our 'post' object after it has been submitted.
app.controller('PostsCtrl', function ($scope) {
    $scope.posts = [];
    $scope.post = {url: 'http://', title: ''};

    $scope.submitPost = function () {
      $scope.posts.push($scope.post);
      $scope.post = {url: 'http://', title: ''};
    };
  });

Since our users are creating new posts by filling out a form, we should trigger this method when the form has been submitted. AngularJS makes this really easy with the 'ng-submit' directive. It's very similar to JavaScript's 'onsubmit'. ng-submit will execute any expression you pass to it when the form has been submitted. In our case, we want it to execute 'submitPost()'.

In views/posts.html, call submitPost() using ng-submit when the form has been submitted. Lets also show the contents of our 'posts' and 'post' variables to see if our code is working.
<form ng-submit="submitPost()">
  <input type="text" ng-model="post.title" />
  <input type="text" ng-model="post.url" />
  <input type="submit" value="Add Post" />
</form>
Posts: {{ posts }}<br />
Post: {{ post }}

Create a few new posts using your form. When you submit the form, you should see the posts array at the bottom of the page add the post object containing a URL and a title. It's inconvenient to look at an array of information, so instead of just showing the content of our posts array, lets iterate through the array and spit out custom HTML. When working with sets of data, it is common to need to repeat the same UI element over and over again with values from each object in the set. AngularJS has a directive for this called 'ng-repeat'.

In the posts view, create a div using ng-repeat that will iterate through all of our posts and display them as a link.

<div ng-repeat="post in posts">
  <a ng-href="{{ post.url }}">{{ post.title }}</a>
</div>

<form ng-submit="submitPost()">
  <input type="text" ng-model="post.title" />
  <input type="text" ng-model="post.url" />
  <input type="submit" value="Add Post" />
</form>
Posts: {{ posts }}<br />
Post: {{ post }}

Wait a second, we are using the variable name 'post' in our ng-repeat and it's still working! Our controller has a variable named 'post' as well - what's going on here? Well, it turns out that ng-repeat creates a new scope for each element it creates, so you don't have to worry about your controller's $scope variables clashing with ng-repeat's. In our case, we asked ng-repeat to create a new post object for every item in the array (hence post in posts).

What if our users want to remove a post that they've added? Now that our posts are being displayed in an ng-repeat, we can add a 'delete' button next to the post's link. But how will we know which post needs to be deleted from our posts array? We don't know which post's delete button has been clicked. To solve this problem, ng-repeat has a variable named '$index' that holds the (you guessed it) index of the current object in the data set. Therefore, to remove posts, we can pass this index along to a scope method that will splice the object out of our posts array.

Create a scope method that removes an object from the posts array using the index of the object.
$scope.deletePost = function (index) {
  $scope.posts.splice(index, 1);
};

In our view, add a delete button that executes the deletePost() method. Notice that we're using 'ng-click' to do this, which is the AngularJS version of JavaScript's 'onclick'. ng-click will execute any expression that you pass to it - in this case, it's our deletePost() method.

<div ng-repeat="post in posts">
  <a ng-href="{{ post.url }}">{{ post.title }}</a>
  <a ng-click="deletePost($index)">delete</a>
</div>

When you click on the delete button next to any of your posts, it will remove it from the posts array. Notice how the post is also automatically removed from the view; this is the power of two-way data binding. Whenever your posts array is changed, ng-repeat will update itself accordingly.

You've probably noticed that all of your posts are wiped out when you refresh the page. Instead of storing the posts in our browser's memory, we will need to store & access them from a server. In the next section we will create a service that will manage server interactions for our posts.

Communicating with a Server Using a Service and $resource

Interacting with servers is an integral part of most web applications. Typically you would use jQuery's $.ajax function (or any equivalent) to request data from a server and update the DOM. AngularJS has a function called $http that is very similar to $.ajax - it can request data from a server, receive the response, and then execute code on success/error.

Before we get started, lets set up our server. This will take a grand total of thirty seconds, thanks to Firebase. Click here and enter an email and password - be sure to use this link, as Firebase will automatically set up a free server instance in your account for this tutorial. After you've signed up, you should see the Firebase instance created for you. This is what you'll use to store the data for your ang-news application - note the URL of the Firebase instance as we'll need it in the next step (it should look like https://FIREBASE-URL.firebaseIO.com).

Creating the Post Service

Lets create a service called 'Post' that allows us to perform CRUD operations on posts. We can do this by creating a factory and injecting $resource, and our factory will just return $resource("https://FIREBASE-URL.firebaseIO.com/posts/:id.json"). In the 'scripts' folder, create a folder called 'services', and then create a file in it named 'post.js' with our factory returning the resource object.
'use strict';

app.factory('Post', function ($resource) {
  return $resource('https://FIREBASE-URL.firebaseIO.com/posts/:id.json');
});

Note that we added posts/:id.json onto the end of our Firebase URL. The :id denotes an optional paramater, in this case a post ID. If the post ID is present, all request types (POST, GET, DELETE) will be made to '/posts/POSTID-HERE.json'; otherwise, all request types will be made to just '/posts.json'. The former will be used to update, delete and read a specific post. The latter will be used manage the posts object - in our case, this should only entail the creation of new posts and getting the list of all our posts. Firebase stores all data in objects, and specifying posts.json tells Firebase to store all of our posts in an object called 'posts'. This Firebase URL is fully RESTful: to add a post, simply make a POST request; to get all of our posts, make a GET request; etc. As you'll see when we wire this service to our controller, $resource 'just works' and will do all of this behind the scenes for us.

In order for us to use our service, we need to be sure to include this file in our index.html
<script src="scripts/services/post.js"></script>
Once we do that, we'll be able to inject our service into our controller.
app.controller('PostsCtrl', function ($scope, Post) {

Using the Post Service

We can now start using our service to persist data to firebase/our server. We can retrieve our posts by setting $scope.posts = Post.get(). This sends a get request to our resource url without any parameters (so the ':id' part is ignored) which is where we will be storing our posts.

In order for us to have posts returned to us from firebase we need to be able to add them first, so in our $scope.submitPost function, we'll need to use Post.save(post) in order to send a POST request to our posts endpoint with our post object.
$scope.submitPost = function () {
  Post.save($scope.post);
  $scope.post = {url: 'http://', title: ''};
};

Now you should be able to save posts to your server, but you'll notice that when we submit the post, nothing happens, but if we refresh we'll be able to see the post we saved. We'll need to update $scope.posts in order for our view to update, but we only want to do this if the request is successful since we'll need the ID of the saved post. We will no longer identify objects by their $index, as Firebase gives each object a unique ID for us to easily update & delete it from the server.

The save function takes a second parameter as a function for this purpose (success callback). In the callback, we get the object returned from the server as a parameter. Firebase will return the ID in an object that's a reference to the saved post in this format: {name: (postId here)} so we can create a callback to set the item on $scope.posts like so:
Post.save($scope.post, function (ref) {
  $scope.posts[ref.name] = $scope.post;
  $scope.post = {url: 'http://', title: ''};
});

Updating the View for the Controller

We'll need to update $scope.deletePost later in order to work with our service. In order for us to delete a post, we'll need to send a DELETE request to our posts url with the postId at the end, so deletePost needs to take postId as a parameter, and we'll need to change our template so that we pass deletePost the post's id instead of the index (since it's an object and not an array, because Firebase stores all data in objects). Luckily ng-repeat also works for objects, and we can get the key (which is the postId) for each post by changing it to:
<div ng-repeat="(postId, post) in posts">
and update our ng-click for deletePost to
<a ng-click="deletePost(postId)">delete</a>
And now update our delete function to use our post service. Just like creating a new post, we'll need to update $scope.posts in a callback in order for our view to reflect the changes
$scope.deletePost = function (postId) {
  Post.delete({id: postId}, function () {
    delete $scope.posts[postId];
  });
};

You may have noticed that the callback process is somewhat tedious as you have to delete the post in two places (on the server and on $scope). Wouldn't it be great if we only had to update the object on the server, and then the view and controller were updated automatically? With AngularFire, which we'll go over in the next chapter, you get the power of 3 way binding so you'll only need to remove the post on the server, and then the rest will automatically synchronize for you in realtime.

Data Binding with a Real-Time Server

In the last chapter, whenever we saved a post to our server, we had to update our local scope to reflect these changes. With three-way data binding, we can avoid this issue by only worrying about sending our data to the server. Using real-time connections, we can automatically keep the data on our server in sync with our AngularJS application. The creators of AngularJS have stated multiple times that three-way-data binding was how they originally envisioned AngularJS being used, so we'll be utilizing it throughout the rest of the tutorial. You can read more about how exactly data binding works here.

Adding AngularFire to the App

Let's make our app take advantage of our server's realtime power by using AngularFire, a library officially supported by Firebase. We can install AngularFire by typing bower install --save angularfire#0.8.2 into the terminal from our 'ang-news' folder. If you get a message about which version of AngularJS to use, you can use !1 to persist the first option which should be the version of angular that yeoman generated.Now we'll need to require the AngularFire library in our app. We can do so where we defined app in app.js and add 'firebase' as a dependency.
var app = angular
  .module('angNewsApp', [
    'ngAnimate',
    'ngCookies',
    'ngResource',
    'ngRoute',
    'ngSanitize',
    'ngTouch',
    'firebase'
  ])
While we're in app.js let's also create a constant for our firebase url, as we'll need to inject it into our services or controllers.
])
.constant('FIREBASE_URL', 'https://PUT-YOUR-FIREBASE-URL-HERE.firebaseio.com/');

Replacing $resource with $firebase

Now that our app is set up, we can inject $firebase and FIREBASE_URL into our Post service and begin setting up the real-time connection to our firebase. First, we want to create a Firebase reference for our posts. We can do this with new Firebase(FIREBASE_URL + 'posts');. This indicates that there is a root object called 'posts' on our server that we want to open a connection to. If you want to dive a bit deeper into what a firebase reference is, check out this link (https://www.firebase.com/docs/JavaScript/firebase/). We can now create a var posts then pass this reference to the $firebase service which will return an AngularFire object that will contain our data and has some helper functions for adding, deleting, updating and querying for objects within our 'posts' object.
app.factory('Post', function ($firebase, FIREBASE_URL) {
  var ref = new Firebase(FIREBASE_URL);
  var posts = $firebase(ref.child('posts')).$asArray();
});
You may notice that jshint is saying that Firebase is not defined; this is due to AngularFire declaring a global variable named 'Firebase'. If we just add 'Firebase': false to .jshintrc (just like we did with our global 'app' variable) that should fix the warning.Now we can create some methods for our service to return to help us actually manipulate the data at this location. We can use the methods on posts (see AngularFire API for methods, although many of these are self explanatory [$add, $remove, $save, $child, $set, $getIndex]) to help us do so.
function ($firebase, FIREBASE_URL) {
  var ref = new Firebase(FIREBASE_URL);
  var posts = $firebase(ref.child('posts')).$asArray();

  var Post = {
    all: posts,
    create: function (post) {
      return posts.$add(post);
    },
    get: function (postId) {
      return $firebase(ref.child('posts').child(postId)).$asObject();
    },
    delete: function (post) {
      return posts.$remove(post);
    }
  };

  return Post;
}

Notice how we've had to change the splice() method to the $remove() method. Synchronized arrays, while still JavaScript arrays, should only be manipulated using AngularFire methods to ensure all changes are properly synced.

And now we can update our controller to use Firebase. The AngularFire methods return a promise which we can then chain with .then and pass a function that will get called once the operation completes (similar to how we used the callback with $resource). We also no longer need to update $scope.posts since the data is being synchronized with firebase (sweet, right?).
app.controller('PostsCtrl', function ($scope, Post) {
  $scope.posts = Post.all;

  $scope.post = {url: 'http://', 'title': ''};

  $scope.submitPost = function () {
    Post.create($scope.post).then(function () {
      $scope.post = {url: 'http://', 'title': ''};
    });
  };

  $scope.deletePost = function (post) {
    Post.delete(post);
  };

});
We'll have to update our view so that it works with our updated controller. We need to use just a plain ng-repeat for post in posts since the firebase service is returning an array, and we'll be passing our post object to our delete function instead of postId
<div ng-repeat="post in posts">
  <a ng-href="{{ post.url }}">{{ post.title }}</a>
  <a ng-click="deletePost(post)">delete</a>
</div>

Now we should be able to add and remove posts that persist to our firebase in realtime!

Setting Up the Comments Page

A integral part of social link sharing sites is the comments page for each link that is posted. Lets go ahead and set up a view for individual posts where we can eventually add commenting functionality (we'll do this in a few chapters).

Create a controller called 'postview.js'. In our post view controller, we need to load the specific post for this page. To do this, we'll grab the post's ID from the URL using $routeParams and then call the 'get' method from our Post service.
'use strict';

app.controller('PostViewCtrl', function ($scope, $routeParams, Post) {
  $scope.post = Post.get($routeParams.postId);
});
Create a view called 'showpost.html'. For now, we'll just have a link to the website in our post's URL and have another link that can take us back to our homepage.
<div>
  <a ng-href="{{ post.url }}">{{ post.title }}</a>
</div>

<a ng-href="#/posts">Back to Posts</a>
Add our new JavaScript files to index.html.
<!-- build:js({.tmp,app}) scripts/scripts.js -->
<script src="scripts/app.js"></script>
<script src="scripts/controllers/posts.js"></script>
<script src="scripts/controllers/postview.js"></script>
<script src="scripts/services/post.js"></script>
<!-- endbuild -->
Now lets create a route for '/posts/:postId' in app.js. The ':postId' is retrieved by our controller using $routeParams (notice how the variables line up - $routeParams.postId and /posts/:postId). For this route, we'll set the template to our 'showpost.html' and we'll inject our 'PostViewCtrl'.
$routeProvider
  .when('/', {
    templateUrl: 'views/posts.html',
    controller: 'PostsCtrl'
  })
  .when('/posts/:postId', {
    templateUrl: 'views/showpost.html',
    controller: 'PostViewCtrl'
  })
  .otherwise({
    redirectTo: '/'
  });
Our post view should be working now. To test it out, make a link to post page from the ng-repeat in posts.html and then click on one of the 'comments' links.
<div ng-repeat="post in posts">
  <a ng-href="{{ post.url }}">{{ post.title }}</a>
  <a ng-href="#/posts/{{ post.$id }}">comments</a>
  <a ng-click="deletePost(post)">delete</a>
</div>
The final touch we will add will be on our submitPost method in our 'PostsCtrl'. Wouldn't it be neat if our application redirected us to the post page after we submitted a post? We can do this with $location. Inject $location into our 'PostsCtrl' and then call $location.path('/posts/' + ref.name()); after our Post service successfully creates the post.
'use strict';

app.controller('PostsCtrl', function ($scope, $location, Post) {
  $scope.posts = Post.all;

  $scope.post = {url: 'http://'};

  $scope.submitPost = function () {
    Post.create($scope.post).then(function (ref) {
      $location.path('/posts/' + ref.name());
    });
  };

Now that we have the base of our application set up and wired to our server with three way data binding, we need to spend some time building out our application's user interface. In the next chapter, we'll set up our site's HTML structure, create a nav bar by utilizing ng-include, and create a filter for formatting our URLs.

Using ng-include to Create a Navbar & Filters to Format Data

Right now our application doesn't look pretty - it's just a list of links and a form with no styling. In this chapter we will spend some time developing our user interface by utilizing filters and ng-include.

First, we need to add some CSS styling to make our app pretty. Replace the existing content of your main.css file with the CSS from here

Adding a Navbar to the Application

Let's add a navbar to our app by creating a new view called 'nav.html' and including it with an ng-include in our index.html file. Our navbar will have a few components:

  • A form to submit new posts
  • A link to the home page of our app (where we see all of our posts)
Create 'nav.html' in the 'views' folder with the following HTML (feel free to copy and paste):
<nav class="navbar navbar-default" role="navigation">
  <!-- Brand and toggle get grouped for better mobile display -->
  <div class="navbar-header">
    <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
      <span class="sr-only">Toggle navigation</span>
      <span class="icon-bar"></span>
      <span class="icon-bar"></span>
      <span class="icon-bar"></span>
    </button>
    <a class="navbar-brand" href="#">ng-news</a>
  </div>

  <!-- Collect the nav links, forms, and other content for toggling -->
  <div ng-controller="NavCtrl" class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
    <form class="navbar-form navbar-left" role="search" ng-submit="submitPost()">
      <div class="form-group">
        <input type="text" class="form-control" placeholder="Title" ng-model="post.title">
      </div>
      <div class="form-group">
      <input type="text" class="form-control" placeholder="Link" ng-model="post.url">
      </div>
      <button type="submit" class="btn btn-default">Submit</button>
    </form>
  </div><!-- /.navbar-collapse -->
</nav>
Notice that <a class="navbar-brand" href="#">ng-news</a> is linking to our root view. We also set up a controller called 'NavCtrl' that will handle submitting posts in <div ng-controller="NavCtrl". Our NavCtrl will now be responsible for adding posts, so we should move our submitPost method out of PostsCtrl and into NavCtrl. Lets set up our NavCtrl:
'use strict';

app.controller('NavCtrl', function ($scope, $location, Post) {
  $scope.post = {url: 'http://', title: ''};

  $scope.submitPost = function () {
    Post.create($scope.post).then(function (ref) {
      $location.path('/posts/' + ref.name());
      $scope.post = {url: 'http://', title: ''};
    });
  };

});
Now we can add nav.js controller to our index.html. While we're in index.html, lets add our nav to index.html using ng-include. Replace the entire <div class='container'> element with the following:
<!-- Add your site or application content here -->
<div ng-include="'views/nav.html'"></div>
<div class="container" ng-view=""></div>


<!-- Google Analytics: change UA-XXXXX-X to be your site's ID -->

This will include the navbar template on every page.

Since we no longer will be adding posts from the form in posts.html, go ahead and remove the submitPost method from PostsCtrl.Now lets change out posts.html because the form shouldn't be there. Also, we should style it. Feel free to copy and paste this code, as we're only reformatting our HTML.
<div class="container posts-page">
  <div class="post row" ng-repeat="post in posts">
    <div class="col-xs-1">
    </div>
    <div class="col-md-9 col-xs-11">

      <div class="info">
        <a ng-href="{{ post.url }}">
          {{ post.title }}
          <span class="url">({{ post.url }})</span>
        </a>
      </div>
      <div>
        <span>submitted by <a ng-href="#/users/{{ post.creatorUID }}">{{ post.creator }}</a></span>
        &mdash;
        <a ng-href="#/posts/{{ post.$id }}">comments</a>
        <a ng-click="deletePost(post)">delete</a>
      </div>
    </div>
  </div>
</div>

Creating a Filter for Formatting URLs

At this point, our application should look really slick. Lets make one last change that many social link sharing sites have-- showing the host URL (x.com instead of http://www.x.com) next to every post's link. This way users can know what they are clicking on before they leave our site.

To do this, lets create a filter. We'll have it create an a element, set the href to our URL, and the ask for the a element's hostname.
'use strict';

app.filter('hostnameFromUrl', function () {
  return function (str) {
    var url = document.createElement('a');

    url.href = str;

    return url.hostname;
  };
});
Before we can use our hostnameFromUrl filter, we need to include it in index.html.
<!-- build:js({.tmp,app}) scripts/scripts.js -->
<script src="scripts/app.js"></script>
<script src="scripts/controllers/posts.js"></script>
<script src="scripts/controllers/postview.js"></script>
<script src="scripts/controllers/nav.js"></script>
<script src="scripts/services/post.js"></script>
<script src="scripts/filters/url.js"></script>
<!-- endbuild -->
Now in posts.html, lets show the hostname of our post's URL next to the post title.
<div class="info">
  <a ng-href="{{ post.url }}">
    {{ post.title }}
    <span class="url">({{ post.url | hostnameFromUrl }})</span>
  </a>
</div>

Filters are incredibly useful for formatting strings and you'll use them often, but they can also be used for arrays if you only want to use part of the data in the array.

You may have noticed that anyone who comes to our site can add and delete links anonymously. This could lead to spam, trolls, and hateful comments cluttering our beautiful site. To combat this, we'll need to force users to sign up or login before posting & deleting links. In the next chapter, we'll get started by creating a service to authenticate our users.

Authenticating Users with a Service

Registering and signing in users is a common feature of most web applications. While the general idea isn't terribly complex, ensuring that you are authenticating users securely is another matter. We cannot trust the client to handle security concerns, as the browser can easily be manipulated and injected with malicious code. Therefore, the hard work of authentication must happen on the server, with the client only responsible for transmitting and receiving authentication data.

Creating a Service for Authentication

Firebase has a secure authentication service built in called "Firebase Simple Login". This automatically takes care of all security concerns for authenticating users, which will allow us to solely focus on building the AngularJS code needed to interact with it. Firebase Simple Login allows for authentication through Facebook, Twitter, Github, or even custom authentication solutions. For our app, well use an email/password pair for each individual user, which is also supported by Firebase's Simple Login.

To use Firebase Simple Login, we'll need to enable it in our firebase settings. In the Firebase account dashboard, navigate to the 'Auth' section. Under the 'Authentication Providers' header, select the 'Email & Password' option, and check 'Enable'.Let's create a service for authentication called Auth where we'll be injecting the $firebaseSimpleLogin service. We'll make a reference to our firebase and pass it along to $firebaseSimpleLogin. Then, we'll create a method for registering the user. The $firebaseSimpleLogin service provides a $createUser function we can use that takes an email and password. After that, we'll create a method for returning a promise that resolves to the currently logged in user. We'll use this method in conjunction with the resolve property of ngRoute to make sure the authentication state is resolved before entering certain states. Finally, we'll add a user object that will be populated with the logged in user (if the person is logged in) and a function (signedIn()) that will return true or false based off the login status. We're also going to need to add some code to update the login status in real time.
'use strict';

app.factory('Auth', function ($firebaseSimpleLogin, FIREBASE_URL, $rootScope) {
  var ref = new Firebase(FIREBASE_URL);
  var auth = $firebaseSimpleLogin(ref);

  var Auth = {
    register: function (user) {
      return auth.$createUser(user.email, user.password);
    },
    login: function (user) {
      return auth.$login('password', user);
    },
    logout: function () {
      auth.$logout();
    },
    resolveUser: function() {
      return auth.$getCurrentUser();
    },
    signedIn: function() {
      return !!Auth.user.provider;
    },
    user: {}
  };

  $rootScope.$on('$firebaseSimpleLogin:login', function(e, user) {
    console.log('logged in');
    angular.copy(user, Auth.user);
  });
  $rootScope.$on('$firebaseSimpleLogin:logout', function() {
    console.log('logged out');
    angular.copy({}, Auth.user);
  });

  return Auth;
});
There are a few things at play here. First, notice that we've injected $rootScope into our service. $rootScope is similar to $scope but is global across the entire application. As such, when doing anything with $rootScope, you should be sure to follow best practices for using global variables, which is to say, avoid using them. In our situation, however, AngularFire broadcasts login/logout events using the $rootScope which we can tap into to update our Auth.user object. Notice that we are using angular.copy() to make the update. It is important that we use this method because it allows all references pointing to Auth.user to be updated during authentication state change as well.To let our users sign up and login, we'll need a registration & login page. Let's create a controller that will be used for both the login and register page. They both will be using our Auth service, so we'll need to include Auth in our AuthCtrl.

Creating a Controller for Authentication

First, let's configure a route that will instantiate the controller we're about to make. We use the resolve property to define dependencies that needs to be resolved before our controller is instantiated. In this case, we want to make sure we know the authentication status of the user so we can seamlessly redirect them to the homepage if they're already logged in. Notice that we're also defining a user property that we can inject into our controller.
.when('/register', {
  templateUrl: 'views/register.html',
  controller: 'AuthCtrl',
  resolve: {
    user: function(Auth) {
      return Auth.resolveUser();
    }
  }
})
Now lets define the controller. See how we're injecting user from the resolve property? If it's populated (meaning they are already logged in), automatically redirect them to the homepage ($location.path('/');). After that, set up a register method that takes a user object from the $scope and sends it to Auth.register. We'll need to call Auth.login on our user object after registration is successful in order to log in the user we created. If that succeeds, we can then redirect them to the homepage.
'use strict';

app.controller('AuthCtrl', function ($scope, $location, Auth, user) {
  if (user) {
    $location.path('/');
  }

  $scope.register = function () {
    Auth.register($scope.user).then(function() {
      return Auth.login($scope.user).then(function() {
        $location.path('/');
      });
    });
  };
});
Include both our controller and service in our index.html
<script src="scripts/controllers/auth.js"></script>
<script src="scripts/services/auth.js"></script>

Creating Views for Authentication

In views/register.html add:

<div class="auth-forms">

  <h2>Register</h2>

  <form ng-submit="register()">
    <input type="email" ng-model="user.email" placeholder="Email" class="form-control"><br>
    <input type="password" ng-model="user.password" placeholder="Password" class="form-control"><br>
    <input type="submit" value="Register" class="btn btn-primary" />
  </form>

</div>

You should now be able to sign up for our site successfully. However, we should update our navbar to show a logout button when our user is signed in.

First, we'll need to bind both the Auth.signedIn() and Auth.logout() methods from our Auth service to our NavCtrl (be sure to include Auth as a dependency for NavCtrl).
$scope.signedIn = Auth.signedIn;
$scope.logout = Auth.logout;
After our form in nav.html, add an unordered list with a list element containing a logout link.
</form>
<ul class="nav navbar-nav navbar-right">
  <li ng-show="signedIn()">
    <a ng-href="#" ng-click="logout()">Logout</a>
  </li>
</ul>

Now when we register, we should be redirected to the posts page and a log out button should appear in the navbar. You should also be able to see a user created in the Firebase account dashboard (the account section is not real-time so you'll need to refresh and hit accounts every time to see the most up-to-date list).

Now let's log out and create our login page. Add a login() method to the AuthCtrl that passes a user object to the Auth.login() method:
'use strict';

app.controller('AuthCtrl',
  function ($scope, $location, Auth) {
    if (Auth.signedIn()) {
      $location.path('/');
    }

    $scope.login = function () {
      Auth.login($scope.user).then(function () {
        $location.path('/')
      })
    };

    $scope.register = function () {
Then, create a view for the login page.
<div class="auth-forms">

  <h2>Log In</h2>

  <form ng-submit="login()">
    <input type="email" ng-model="user.email" placeholder="Email" class="form-control"><br>
    <input type="password" ng-model="user.password" placeholder="Password" class="form-control"><br>
    <input type="submit" value="Log in" class="btn btn-primary" />
  </form>

</div>
Next, add a new route. Like the '/register' route, use the resolve parameter to ensure the authentication state resolved before the view is rendered.
.when('/login', {
  templateUrl: 'views/login.html',
  controller: 'AuthCtrl',
  resolve: {
    user: function(Auth) {
      return Auth.resolveUser();
    }
  }
})
Finally, include in the nav bar a link to the login page. While you're at it, also include one to the register page. Be sure that these two links are only visible to users who are not logged in.
<ul class="nav navbar-nav navbar-right">
  <li ng-show="signedIn()">
    <a href="#" ng-click="logout()">Logout</a>
  </li>
  <li ng-hide="signedIn()">
    <a href="#/login">Login</a>
  </li>
  <li ng-hide="signedIn()">
    <a href="#/register">Register</a>
  </li>
</ul>

And now we should be able to log the user we just created in and out!

Error Handling for the Authentication Forms

You might notice that when we fail to log in nothing happens right now, as we haven't handled any errors that firebase returns yet. Firebase uses promises to return data (see $q documentation, right now we have a success callback but no error callback). All we need to do is pass a second function to the .then() method, which will be called if there's an error. The function will be provided with an error object, which can then be used to set the contents to a scope variable called error. We'll show the contents of the error variable in our view.
'use strict';

app.controller('AuthCtrl', function ($scope, $location, Auth, user) {
  if (user) {
    $location.path('/');
  }

  $scope.login = function () {
    Auth.login($scope.user).then(function () {
      $location.path('/');
    }, function (error) {
      $scope.error = error.toString();
    });
  };

  $scope.register = function () {
    Auth.register($scope.user).then(function() {
      return Auth.login($scope.user).then(function() {
        $location.path('/');
      });
    }, function(error) {
      $scope.error = error.toString();
    });
  };
});
Then in our view, we can simply have a p tag with ng-show="error" that will display our errors. If there aren't any errors, the p tag won't show up.
<div class="auth-forms">

  <h2>Log In</h2>

  <form ng-submit="login()">
    <p ng-show="error" class="text-danger">{{ error }}</p>
    <input type="email" ng-model="user.email" placeholder="Email" class="form-control"><br>
    <input type="password" ng-model="user.password" placeholder="Password" class="form-control"><br>
    <input type="submit" value="Log in" class="btn btn-primary" />
  </form>

</div>
<div class="auth-forms">

  <h2>Register</h2>

  <form ng-submit="register()">
    <p ng-show="error" class="text-danger">{{ error }}</p>
    <input type="email" ng-model="user.email" placeholder="Email" class="form-control"><br>
    <input type="password" ng-model="user.password" placeholder="Password" class="form-control"><br>
    <input type="submit" value="Register" class="btn btn-primary" />
  </form>

</div>

If we try logging in with an invalid email/password, or if someone has already created an account with our email, we should now see these errors displayed in our view.

In the next chapter we will add the ability to store other user information. Bear in mind that Firebase Simple Login cannot store all of our user data. The Firebase auth object contains the user's email and a unique ID string (called 'uid'), nothing more. If we want to store additional information about the user, we will need to create a separate "model" that is keyed with the uid Firebase returns.

Storing Custom Data with Firebase

While firebase's auth contains a user object, we aren't able to store custom data in it. Instead, we'll create a custom users collection in Firebase that will use the uid from the auth object to key profile information about the particular user. Since we don't want to display emails anywhere on the site, the first thing we'll store in the profile is a username.

Creating the Profile

Let's start by adding a profile when a user first registers-- We'll do this by creating a new function called createProfile() in the Auth service. This function will save the user's profile to the location <FIREBASE_URL>/profile/<uid> and will be called after we register the user and log them in (make sure you injects the $firebase service into the Auth service):
register: function (user) {
  return auth.$createUser(user.email, user.password);
},
createProfile: function (user) {
  var profile = {
    username: user.username,
    md5_hash: user.md5_hash
  };

  var profileRef = $firebase(ref.child('profile'));
  return profileRef.$set(user.uid, profile);
},

You'll notice that in addition to the username, we're saving a property md5_hash, which is not something we ask the user for. This, among others, is a property returned by Firebase after we register a user.

To see what the full user object looks like, we can add a console.log(Auth.user) to the function that is called when the $firebaseSimpleLogin:login event is triggered in the auth service. You should see a few different data attributes such as email, provider, and uid. You should also see the md5_hash property mentioned above, which is a hash of our email address that can be used to access our Gravatar image.Finally, we need to ensure our createProfile() function is called after a user registers. In the AuthCtrl's register() function, call it after we login the user:
$scope.register = function () {
  Auth.register($scope.user).then(function(user) {
    return Auth.login($scope.user).then(function() {
      user.username = $scope.user.username;
      return Auth.createProfile(user);
    }).then(function() {
      $location.path('/');
    });
  }, function(error) {
    $scope.error = error.toString();
  });
};
Our users need to specify what username they would like to have, so we need to update our view with a text field for their desired username:
<input type="text" ng-model="user.username" placeholder="Username" class="form-control"><br>

Now when a user registers, their username and md5_hash should be saved in Firebase. You can confirm this is the case through Firebase's account dashboard.

Accessing the Current User's Profile Information

Now that we create and save a user's profile, we're we can retrieve that information when a user logs on. To easily do this, we'll create a Firebase reference and store it in the Auth.user object during the $firebaseSimpleLogin:login event:
$rootScope.$on('$firebaseSimpleLogin:login', function(e, user) {
  angular.copy(user, Auth.user);
  Auth.user.profile = $firebase(ref.child('profile').child(Auth.user.uid)).$asObject();

  console.log(Auth.user);
});
Notice how we're using AngularFire's $asObject() method to create a dynamic link to a user's profile. Because this link stays open, we're also going to want to be sure to destroy it using the $destroy() method when a user logs out:
$rootScope.$on('$firebaseSimpleLogin:logout', function() {
  console.log('logged out');

  if(Auth.user && Auth.user.profile) {
    Auth.user.profile.$destroy();
  }
  angular.copy({}, Auth.user);
});

Updating the Navbar to Show the Current User's Information

Now that we have access to a user's profile, let's spice up our navbar. First, bind the Auth.user object to $scope.user in our nav controller: $scope.user = Auth.user;. Then in our nav template, add list elements that contains our Gravatar image (using our md5_hash) and our username.
<ul class="nav navbar-nav navbar-right" >
  <li ng-show="signedIn()">
    <p class="navbar-text">{{ user.profile.username }}</p>
  </li>
  <li ng-show="signedIn()">
    <a ng-href="#/users/{{ user.profile.uid }}">
      <img ng-src="http://www.gravatar.com/avatar/{{ user.profile.md5_hash }}" class="nav-pic" />
      {{ currentUser.username }}
    </a>
  </li>
  <li ng-show="signedIn()">
    <a href="#" ng-click="logout()">Logout</a>
  </li>
  <li ng-hide="signedIn()">
    <a href="#/login">Login</a>
  </li>
  <li ng-hide="signedIn()">
    <a href="#/register">Register</a>
  </li>
</ul>

Associating Users with Posts

Now that we've implemented authentication, we can associate user information with posts. In our nav controller, simply add new fields for uid and username. You may be wondering why we want to store the username when we can simply retrieve it using the uid. This is a concept called called denormalization and makes it easier and faster to retrieve the data we care about. The downside of doing this is that if we were to change a user's username, we'd have to update it across all their posts-- but that isn't a feature we're going to implement.
$scope.submitPost = function () {
  $scope.post.creator = $scope.user.profile.username;
  $scope.post.creatorUID = $scope.user.uid;
  Post.create($scope.post).then(function (ref) {
    $location.path('/posts/' + ref.name());
    $scope.post = {url: 'http://', title: ''};
  });
};
While we're in the business of associating users with posts, let's go ahead and associate posts back with users! In our post service, use then() to add a function that'll be called after the create() function. This function should store the ID of the post (returned via postRef.name()) in a collection located at /user_posts/<uid>/. Maintaining this collection will allow us to easily retrieve a list of posts for a give user, which we'll need when we construct the profile page later on:
var Post = {
  all: posts,
  create: function (post) {
    return posts.$add(post).then(function(postRef) {
      $firebase(ref.child('user_posts').child(post.creatorUID))
                        .$push(postRef.name());
      return postRef;
    });
  },
  get: function (postId) {

By storing this list of posts in a different collection than /profile/<uid>, we're able to retrieve a users username and md5_hash without also having to download a list of all their posts.

Hiding Elements from Unauthenticated Users

Now that we're associating posts with users, it makes sense that only those who are logged in can post. We already know about the ng-show directive, so go ahead and use that to hide the form in the nav template:
<form class="navbar-form navbar-left" role="search" ng-submit="submitPost()" ng-show="signedIn()">
Additionally, we only want the creator of a post to be able to delete the post. Since we're storing the creator's uid we can use that to check if we should show the delete option. In the PostsCtrl, first inject the Auth service, then bind the user to $scope:
app.controller('PostsCtrl', function ($scope, $location, Post, Auth) {
  $scope.posts = Post.all;
  $scope.user = Auth.user;
Then in the posts view, hide the 'delete' link if the current user's uid doesn't match that of the creator's. You can test this works by logging out.
<a ng-click="deletePost(post)" ng-show="user.uid === post.creatorUID">delete</a>

Even though the submit form is hidden, it won't necessarily stop a malicious user from posting without logging in. We'll fix this issue later on using Firebase security rules.

Now you should have a fully functional User model, where only logged in users can create posts and only post owners can delete them! In the next chapter, we are going to implement commenting and user profiles!

Adding Comments in Firebase

Now that we've built out our User functionality, we can finally go ahead and make comments on our posts.

Let's start by updating our post service. We're going to be storing the comment data at comments/<postId>. Our function should take postId (the id of the post we're commenting on):
comments: function (postId) {
  return $firebase(ref.child('comments').child(postId)).$asArray();
}
Now, we can use this method in PostViewCtrl to retrieve a handle to the comments object. We can use this handle to create an addComment() method that can be used to (you guessed it!) add comments:
'use strict';

app.controller('PostViewCtrl', function ($scope, $routeParams, Post, Auth) {
  $scope.post = Post.get($routeParams.postId);
  $scope.comments = Post.comments($routeParams.postId);

  $scope.user = Auth.user;
  $scope.signedIn = Auth.signedIn;

  $scope.addComment = function () {
    if(!$scope.commentText || $scope.commentText === '') {
      return;
    }

    var comment = {
      text: $scope.commentText,
      creator: $scope.user.profile.username,
      creatorUID: $scope.user.uid
    };
    $scope.comments.$add(comment);

    $scope.commentText = '';
  };

});
Now we'll need to update our template in order to both show the comments and submit new ones. We'll use ng-repeat to display the comments. Then we'll create the form for submitting comments. We'll only want the form to show up if a user is signed in, so we can use ng-show="signedIn()" for that. If the user is not logged in we can just show them a link to the log in page. In the form, all we'll need is a textarea for comment.text and a button for submitting will call addComment()
<div class="container posts-page">

  <div class="post row">
    <div class="col-xs-1">
    </div>
    <div class="col-md-9 col-xs-11">
      <div class="info">
        <a ng-href="{{ post.url }}">
          {{ post.title }}
          <span class="url">({{ post.url | hostnameFromUrl }})</span>
        </a>
      </div>
      <div>
        <span>
          submitted by
          <a ng-href="#/users/{{ post.creatorUID }}">
            {{ post.creator }}
          </a>
        </span>
      </div>
    </div>
    <div class="col-md-2">
    </div>
  </div>

  <div ng-repeat="comment in comments" class="row cmt">
    <div class="col-md-12">
      <p>{{ comment.text }}</p>
      <p class="author">posted by
        <a ng-href="#/users/{{ comment.creatorUID }}">
          {{ comment.creator }}
        </a>
      </p>
    </div>
  </div>

  <div class="cmt-form">
    <div ng-hide="signedIn()">
      <p><a href="#/login">Sign in</a> to post a comment</p>
    </div>

    <form ng-show="signedIn()" ng-submit="addComment()">
      <textarea
        ng-model="commentText"
        placeholder="Post a Comment"
        class="form-control"></textarea><br />
      <input type="submit" value="Post Comment" class="btn btn-primary" />
    </form>
  </div>

</div>

Once that's done we should be able to add comments to posts and see comments that have already been added. One benefit of using Firebase here is the comments will be synced in real-time so your users won't have to resort to the F5 hammer during flame wars.

Now that we're able to create comments, let's give users the ability to delete their own comments. In our PostViewCtrl let's add a deleteComment() method that'll only be visible if the user created the comment:
$scope.deleteComment = function (comment) {
  $scope.comments.$remove(comment);
};
Finally, let's only show the delete link to the creator of the comment:
<div ng-repeat="comment in comments" class="row cmt">
  <div class="col-md-12">
    <p>{{ comment.text }}</p>
    <p class="author">posted by
      <a ng-href="#/users/{{ comment.creatorUID }}">
        {{ comment.creator }}
      </a>
      <a ng-href=""
        ng-click="deleteComment(comment)"
        ng-show="signedIn() && comment.creatorUID === user.uid">
        (delete)
      </a>
    </p>
  </div>
</div>

Viewing a User Profile

Now that we have a fully functional comment system, let's create the user profile, which will display posts that have been created by the user. We'll need to create a view and controller for the profile. The route we'll be using is "/users/:uid"

Let's start off by creating a new route in app.js:
.when('/users/:userId', {
  templateUrl: 'views/profile.html',
  controller: 'ProfileCtrl'
})
Because we want to abstract the data retrieval into services as much as possible, let's go ahead and create a new 'Profile' service for retrieving information (as always, include it in index.html):
'use strict';

app.factory('Profile', function ($window, FIREBASE_URL, $firebase, Post, $q) {
  var ref = new $window.Firebase(FIREBASE_URL);

  var profile = {
    get: function (userId) {
      return $firebase(ref.child('profile').child(userId)).$asObject();
    },
    getPosts: function(userId) {
      var defer = $q.defer();

      $firebase(ref.child('user_posts').child(userId))
        .$asArray()
        .$loaded()
        .then(function(data) {
          var posts = {};

          for(var i = 0; i<data.length; i++) {
            var value = data[i].$value;
            posts[value] = Post.get(value);
          }
          defer.resolve(posts);
        });

      return defer.promise;
    }
  };

  return profile;
});

The get() function is pretty self explanatory but the getPosts() requires a little more explaining. If you recall, when a user created a post, we crated two records: on being the actual post itself and the other containing the ID of the new post in an array under the user's uid. In order to retrieve a user's post, we need to first query for the array of posts and then query for each individual post from that array. Because we're making several queries to Firebase at once, we return a promise object that we then resolve with the post data.

Next, go ahead and create the ProfileCtrl (don't forget to include it in index.html!). We can use the $routeParams service to retrieve the uid from the url. One thing to keep in mind is that because getPosts() returns a promise that resolves to the data we want, we need to make sure we're binding $scope.posts after the function finishes executing:
'use strict';

app.controller('ProfileCtrl', function ($scope, $routeParams, Profile) {
  var uid = $routeParams.userId;

  $scope.profile = Profile.get(uid);
  Profile.getPosts(uid).then(function(posts) {
    $scope.posts = posts;
  });
});
Finally, add the template file, which looks similar to the posts.html
<div class="container profile">
  <div class="row">
    <div class="col-sm-1">
      <img ng-src="http://www.gravatar.com/avatar/{{ profile.md5_hash }}" class="prof-img" />
    </div>

    <div class="col-sm-10">
      <span class="name">
        {{ profile.username }}
      </span>
    </div>
  </div>

  <div class="post row" ng-repeat="(key,post) in posts">
    <div class="col-md-8 col-xs-10">
      <div class="info">
        <a ng-href="{{ post.url }}">
          {{ post.title }}
          <span class="url">({{ post.url | hostnameFromUrl }})</span>
        </a>
      </div>
      <div>
        <a ng-href="#/posts/{{ post.$id }}">comments</a>
      </div>
    </div>
  </div>
</div>

Securing Your Data with Security Rules

Up until now, we've relied on hiding DOM elements to restrict users from doing things they aren't authorized to do. While hiding elements will certainly stop the majority of people from messing with your data, what we really want to do is enforce restrictions at the database level-- this is where security rule come into play.

Firebase security rules can be quite confusing, especially at first, but they are pretty powerful and can certainly be used protect your data. If you're interested in learning more, be sure to read the guide.

In Firebase account dashboard, click the "Security Rules" tab on the left side. Then, copy the following JSON and click "Save Rules":
{
  "rules": {

    "posts": {
      // anyone can view posts
      ".read": true,

      "$id": {
        // auth can't be null to make/edit post
        // if the post exists, auth.uid must match creatorUID
        ".write": "(auth != null && !data.exists()) || data.child('creatorUID').val() === auth.uid",

        // We want to make sure that all 4 fields are present before saving a new post
        ".validate": "newData.hasChildren(['title','url','creator','creatorUID'])",

        // title must be a string with length>0
        "title": {
          ".validate": "newData.isString() && newData.val().length > 0"
        },
        "url": {
          ".validate": "newData.isString()"
        },
        "creator": {
          ".validate": "newData.isString()"
        },
        "creatorUID": {
          ".validate": "auth.uid === newData.val() && root.child('profile/'+newData.val()).exists()"
        }
      }
    },

    "comments": {
      ".read": true,

      "$post_id": {
        // make sure the post we're adding comments to exists
        ".validate": "root.child('posts/'+$post_id).exists()",

        "$comment_id": {
          // same write rules as for Post
          ".write": "(auth != null && !data.exists()) || data.child('creatorUID').val() === auth.uid",
          ".validate": "!data.exists() || data.child('creatorUID').val() === auth.uid"
        }
      }
    },

    "profile": {
      ".read": true,

      "$uid": {
        ".write": "!data.exists() && auth.uid === $uid"
      }
    },

    "user_posts": {
      ".read": true,

      "$uid": {
        // only the user can write here
        ".write": "auth.uid === $uid"
      }
    },

    // Don't let users post to other fields
    "$other": { ".validate": false }

  }
}

Read the comments to get a feel for what is going on with the security rules. You'll notice that the structure of this JSON closely resembles the shape of the data our app stores and retrieves from Firebase. In general, there are three properties that can be used to make up security rules: .read, .write, and .validate. The .read property determines who can view what data while the .write controls who can write data. According to the specification for our app, anyone can view comments or posts, but one must be authenticated to create posts or comments. The final property, .validate, can be used to ensure that only a certain type of data (string, bool, etc) is stored or that that data fits a specific criteria. In the case above, we want to ensure that any post that's created has a title that is a string with a length greater than 0.

Test the Security Rules

It's always a good thing to make sure you trust the code you're writing. As such, we recommend testing the security rules. In the posts view, remove the ng-show directive that only reveals the 'delete' link to the original creator. Then, log out of the application and click the button (make sure you have the console open). You should see a warning that looks something like "FIREBASE WARNING: set at /posts/-JYhXgNtoscekVyoftWL failed: permission_denied" and the post should still be there. Make sure to add the ng-show directive back again after testing!

Hosting with Firebase

In addition to providing a phenomenal real-time backend, Firebase also provides static hosting. This allows you to deploy a front-end application (like what we've built) and have Firebase serve it up to the world!

To get started, first install the Firebase CLI tools and login
$ npm install -g firebase-tools
$ firebase login
Then, in the root directory of your app run firebase init. When it asks you for Firebase app: enter your FIREBASE-URL and for Public Directory enter dist. This command will create a new file called firebase.json.We need to make one quick modification to firebase.json: Open it up and add "rules": "security-rules.json". Then create the file security-rules.json in the root project of the app and copy the security rules from above into it. When we deploy the app, the firebase command will use this file to replace the existing security rules.The yeoman generator we initially used includes a grunt task for compiling. Run grunt build, which will compile, minify, and copy your assets to the dist/ folder. Then type firebase deploy and sit back while the contents of the dist folder (and only the dist folder) are uploaded to Firebase's servers. After it's complete, a call to firebase open will automatically open your web browser to your web app, which is now hosted on Firebase and shareable to the world!

Conclusion

Congratulations! If you've made it this far, you should now have a basic Reddit clone up and running on Firebase's hosting. Hopefully you've gotten a good idea about some of the basic concepts of AngularJS and how to interact with a Firebase backend. The app you've made is most certainly not feature complete and we encourage you to take what you've learned and expand on it. Here are some suggestions for ways to improve the app (in the future we may release content covering some or all of these improvements):

Display user's comments in their profile (this can be done similar to how we display their posts)Allow user's to edit their posts and commentsEnforce username uniqueness-- this one is tricky, check out Firebase priorities and see if you can use them to query user profiles by usernameUse the resolve property of ngRoute to retrieve a user's profile and Posts before the ProfileCtrl is loaded.Advanced Users Refactor Post service-- Current the service requires passing a postId to nearly every method. Take a look at extending the AngularFire factories and use this technique to create a factory that returns the standard Firebase reference object with additional methods bound to it for removing the post, adding comments etc.Advanced Users Implement post/comment upvoting-- for this to work properly, you'll need to save the list of uid's of people who have upvoted a post to prevent them from upvoting it multiple times (hint: it would probably help to first refactor the Post service using the extend factory technique)

As always, if you have any question, comments, or concerns or just want to tell us how much you did or didn't learn, email us at hello@thinkster.io or hit us up on twitter @AngularTutorial or @GoThinkster