Outline
At this point, we have the basics of an application - a user can add new posts which are automatically ordered based on the number of upvotes. Up until now, however, our interface has been lacking in the looks department. We can spruce it up a bit using some basic Bootstrap styling.
index.html
file to include Twitter Bootstrap along with a new layout:
<html>
<head>
<title>Flapper News</title>
<link href="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js"></script>
<script src="app.js"></script>
<style> .glyphicon-thumbs-up { cursor:pointer } </style>
</head>
<body ng-app="flapperNews" ng-controller="MainCtrl">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<div class="page-header">
<h1>Flapper News</h1>
</div>
<div ng-repeat="post in posts | orderBy:'-upvotes'">
<span class="glyphicon glyphicon-thumbs-up"
ng-click="incrementUpvotes(post)"></span>
{{post.upvotes}}
<span style="font-size:20px; margin-left:10px;">
<a ng-show="post.link" href="{{post.link}}">
{{post.title}}
</a>
<span ng-hide="post.link">
{{post.title}}
</span>
</span>
</div>
<form ng-submit="addPost()"
style="margin-top:30px;">
<h3>Add a new post</h3>
<div class="form-group">
<input type="text"
class="form-control"
placeholder="Title"
ng-model="title"></input>
</div>
<div class="form-group">
<input type="text"
class="form-control"
placeholder="Link"
ng-model="link"></input>
</div>
<button type="submit" class="btn btn-primary">Post</button>
</form>
</div>
</div>
</body>
</html>
At the top we've included Bootstrap from a CDN. In the body tag, we've made use of Bootstrap's grid system to align our content in the middle of the screen. We've also stylized the posts list and "Add a new post" form to make things a little easier to read. There's a lot more that could be done on this front so feel free to mess around with more styling before (or after) you continue.
Angular Services
Up to this point, we've been storing important data directly in the controller. While this works, it has some disadvantages:
- when the controller goes out of scope, we lose the data
- that data cannot be easily accessed from other controllers or directives
- the data is difficult to mock, which is important when writing automated tests
To rectify this problem, we're going to refactor our $scope.posts
variable into a
service.
My First Service... Is Really a Factory
In Angular, services are declared much like controllers. Inside app.js
,
we're going to attach a new service to our flapperNews
module.
app.js
(our MainCtrl
controller should appear below this):
app.factory('posts', [function(){
// service body
}])
By Angular conventions, lowerCamelCase is used for
factory names that won't be new
'ed.
You may be wondering why we're using the keyword factory instead of service. In angular, factory and service are related in that they are both instances of a third entity called provider.
app.factory('posts', [function(){
var o = {
posts: []
};
return o;
}]);
What we're doing here is creating a new object that has an array property
called posts
. We then return that variable so that our o
object essentially
becomes exposed to any other Angular module that cares to inject it. You'll
note that we could have simply exported the posts
array directly, however, by
exporting an object that contains the posts
array we can add new objects and
methods to our services in the future.
Injecting the Service
Our next step is to inject the service into our controller so we can access its data. Simply add the name of the service as a parameter to the controller we wish to access it from:
MainCtrl
app.controller('MainCtrl', [
'$scope',
'posts',
function($scope, posts){
...
)};
As you'll recall, two-way data-binding only applies to variables bound to
$scope
. To display our array of posts that exist in the posts factory
(posts.posts
), we'll need to set a scope variable in our controller to mirror
the array returned from the service.
$scope.posts
variable in our controller to the posts array in our service:
$scope.posts = posts.posts;
Now any change or modification made to $scope.posts
will be stored in the
service and immediately accessible by any other module that injects the posts
service.
Check your work
You can view the completed & working code for this tutorial here:
Now our application should behave the same way as the previous chapter, but we've added some styles with bootstrap and moved some of our logic for Posts to an AngularJS service.