Thinkster

Now that we have our backend implemented, we're going to wire up the Angular app we made in the first part of this tutorial.

Loading Posts

Our first step is going to be to query our new backend for all posts using the index route. We do this by adding a new function inside our posts service. If we hit http://localhost:3000/posts.json in our browser, we should be able to see an array of all the posts in our database (which is currently empty).

Inject the $http service:
.factory('posts', [
'$http',
function($http){
  ...
});
Implement getAll() to get all the posts in the service within posts.js:
  o.getAll = function() {
    return $http.get('/posts.json').success(function(data){
      angular.copy(data, o.posts);
    });
  };

In this function we're using the Angular $http service to query our posts route. The success() function allows us to bind function that will be executed when the request returns. Because our route will return a list of posts, all we need to do is copy that list to the client side posts object. Notice that we're using the angular.copy() function to do this as it will make our UI update properly.

Now we need to call this function at an appropriate time to load the data. We do this by adding a property called resolve to our home state.

Use the resolve property of ui-router to ensure posts are loaded:
resolve: {
  postPromise: ['posts', function(posts){
    return posts.getAll();
  }]
}

By using the resolve property in this way, we are ensuring that anytime our home state is entered, we will automatically query all posts from our backend before the state actually finishes loading.

Now, when you fire up the server and go to http://localhost:3000/#/home, you should see all the posts that exist in the database.

Creating New Posts

Up next, we need to enable creating new posts. As with loading posts, we're going to do this by adding another method to our posts service:

Add a method for creating new posts:
o.create = function(post) {
  return $http.post('/posts.json', post).success(function(data){
    o.posts.push(data);
  });
};
Now we can modify the $scope.addPost() method in MainCtrl to save posts to the server:
$scope.addPost = function(){
  if(!$scope.title || $scope.title === '') { return; }
  posts.create({
    title: $scope.title,
    link: $scope.link,
  });
  $scope.title = '';
  $scope.link = '';
};

Refresh the page then try adding a new post. You may have noticed that we recieved a failed 422 Unprocessable Entity response from Rails. This is because Rails has csrf protection enabled by default, and Angular isn't sending the token to Rails when we're trying to add a post. Thankfully $http supports sending csrf tokens out of the box as a X-XSRF-TOKEN header, as long as it detects a XSRF-TOKEN cookie with a token in it. There's also happens to be a gem called angular_rails_csrf which we'll add to our project which will have Rails automatically send the cookie to Angular and also validate the header when Angular is submitting data, that way all we need to do is drop in the gem and everything should just work.

Add angular_rails_csrf to your Gemfile and run bundle install

Now if you try refreshing the page and adding a new post, you should immediately see it displayed and if you refresh the page, the post is still there! We now have persistent data.

Upvoting Posts

The last thing we need to wire up on the main page is upvoting posts.

Like in the previous examples, we'll add another method to our service:
o.upvote = function(post) {
  return $http.put('/posts/' + post.id + '/upvote.json')
    .success(function(data){
      post.upvotes += 1;
    });
};
And then in our controller, we simply replace incrementUpvotes() with this:
$scope.incrementUpvotes = function(post) {
  posts.upvote(post);
};

Here we use the put() method to upvote a post. When the call returns successfully, we update our local copy to reflect the changes. Now when you refresh the page, upvotes are persisted.

Finishing Off Comments

If you remember back to when we first wrote the template, we were treating the index of a post in the posts array to be its id field. Now that we are actually dealing with database entries, we need to make sure that when you click on the "Comments" link for a post, the application directs you to the proper URL.

Modify the Comments link to point to the proper route:
<a href="#/posts/{{post.id}}">Comments</a>

When you click on the "Comments" link for a post, you should be directed to a new Angular URL that might look something like http://localhost:3000/#/posts/1

What we are going to do is have Angular automatically load the full post object with comments when we enter this state. Like we did with the home state, we're going to use the resolve property in the route to accomplish this.

First, lets add a simple method in our posts service to retrieve a single post from our server:
o.get = function(id) {
  return $http.get('/posts/' + id + '.json').then(function(res){
    return res.data;
  });
};

Notice that instead of using the success() method we have traditionally used, we are instead using a promise.

Next we add the resolve object to our posts state:
resolve: {
  post: ['$stateParams', 'posts', function($stateParams, posts) {
    return posts.get($stateParams.id);
  }]
}

The Angular ui-router detects we are entering the posts state and will then automatically query the server for the full post object, including comments. Only after the request has returned will the state finish loading.

To get access to the post object we just retrieved in the PostsCtrl, instead of going through the posts service, the specific object will be directly injected into our PostsCtrl.

We can modify our controller to gain access to it like so:
.controller('PostsCtrl', [
'$scope',
'posts',
'post',
function($scope, posts, post){
  $scope.post = post;

...

Notice that we no longer have need to inject $stateParams into our controller. We're still going to want to inject posts to gain access to the methods for manipulating comments, however.

When you refresh the page, you should now see the title of the post displayed.

To enable adding comments, we can use the same technique we used for adding new posts.

First add a method to the posts service:
o.addComment = function(id, comment) {
  return $http.post('/posts/' + id + '/comments.json', comment);
};
Then call it from within the existing addComment() function:
$scope.addComment = function(){
  if($scope.body === '') { return; }
  posts.addComment(post.id, {
    body: $scope.body,
    author: 'user',
  }).success(function(comment) {
    $scope.post.comments.push(comment);
  });
  $scope.body = '';
};

Now, you can add a comment that's persisted to the database.

Our final task is to enable the upvoting of comments:
o.upvoteComment = function(post, comment) {
  return $http.put('/posts/' + post.id + '/comments/'+ comment.id + '/upvote.json')
    .success(function(data){
      comment.upvotes += 1;
    });
};
In PostsCtrl:
$scope.incrementUpvotes = function(comment){
  posts.upvoteComment(post, comment);
};