Outline
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).
$http
service:
.factory('posts', [
'$http',
function($http){
...
});
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.
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:
o.create = function(post) {
return $http.post('/posts.json', post).success(function(data){
o.posts.push(data);
});
};
$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.
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.
o.upvote = function(post) {
return $http.put('/posts/' + post.id + '/upvote.json')
.success(function(data){
post.upvotes += 1;
});
};
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.
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.
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.
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
.
.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
.
posts
service:
o.addComment = function(id, comment) {
return $http.post('/posts/' + id + '/comments.json', comment);
};
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.
o.upvoteComment = function(post, comment) {
return $http.put('/posts/' + post.id + '/comments/'+ comment.id + '/upvote.json')
.success(function(data){
comment.upvotes += 1;
});
};
PostsCtrl
:
$scope.incrementUpvotes = function(comment){
posts.upvoteComment(post, comment);
};