Anatomy of an AngularJS Application - Instant Zurb Foundation 4: Get up and running in an instant with Zurb Foundation 4 Framework (2013)

Instant Zurb Foundation 4: Get up and running in an instant with Zurb Foundation 4 Framework (2013)

4. Anatomy of an AngularJS Application

In this chapter we will implement our example web application with Angular.js. To do this we will need templates and libraries that allow us to do our work. Let’s begin.

4.1 HTML5Boilerplate

We are going to use the HTML5 Boilerplate template with all the elements necessary to begin developing. You can download it from the project website or in their official repository, or simply copy the following file to your app/index.html since we have already made some changes according to our project:

<!doctype html>
<html lang="es-ES">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title></title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Place favicon.ico in the root directory -->
<link rel="stylesheet" href="/stylesheets/main.css">
</head>
<body>
<!--[if lt IE 8]>
<p class="browserupgrade">
You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.
</p>
<![endif]-->

<p>Hola Mundo! Esto es HTML5 Boilerplate.</p>

<!-- Google Analytics: change UA-XXXXX-X to be your site's ID. -->
<script>
(function(b,o,i,l,e,r){b.GoogleAnalyticsObject=l;b[l]||(b[l]=
function(){(b[l].q=b[l].q||[]).push(arguments)});b[l].l=+new Date;
e=o.createElement(i);r=o.getElementsByTagName(i)[0];
e.src='//www.google-analytics.com/analytics.js';
r.parentNode.insertBefore(e,r)}(window,document,'script','ga'));
ga('create','UA-XXXXX-X','auto');ga('send','pageview');
</script>
</body>
</html>

This will be the only complete HTML page that we have, since the rest will be templates that will dynamically load depending on the application status or user interaction. This is what is currently known as Single Page Applications (SPA).

4.2 Installing Dependencies

We are going to install the front end libraries that we need with the tool Bower. First of all, we need to create the file .bowerrc in the project’s root directory with the following content:

{
"directory": "app/lib"
}

This ensures that each library we install remains saved in the directory app/lib. If we didn’t have this file, the libraries would be installed in the default directory bower_components in the root.

We are going to prepare our HTML and implement a new Gulp task that will make life easier. The task is gulp-inject, which will take the files that we have in the style and script folders and inject them as links into the HTML.

We add the following comment tags to the HTML where the CSS links and the JavaScript scripts will go:

<!doctype html>
<html lang="en">
<head>
[...]
<!-- bower:css -->
<!-- endbower -->
<!-- inject:css -->
<!-- endinject -->
</head>
<body>
[...]
<!-- bower:js -->
<!-- endbower -->
<!-- inject:js -->
<!-- endinject -->
</body>
</html>

Lines like the comments <!-- inject:css --> and <!-- inject:js --> will be read by the Gulp task that we are going to write next that will inject the files. And lines like the comments <-- bower:css --> and <!-- bower:js --> will inject the libraries that we install with Bower. It will put them in one place or another in the HTML depending on whether they are style (css) or script (js) files. To implement the task, we must first install the required plugins:

$ npm install --save-dev gulp-inject
$ npm install --save-dev wiredep

Then we can insert the following tasks in Gulpfile.js and update watch and default:

var inject = require('gulp-inject');
var wiredep = require('wiredep').stream;


// Inject into HTML the path of JS scripts and CSS files
gulp.task('inject', function() {
var sources = gulp.src([ './app/scripts/**/*.js', './app/stylesheets/**/*.css' ]);
return gulp.src('index.html', { cwd: './app' })
.pipe(inject(sources, {
read: false,
ignorePath: '/app'
}))
.pipe(gulp.dest('./app'));
});
// Inject the path of Bower dependencies into HTML
gulp.task('wiredep', function () {
gulp.src('./app/index.html')
.pipe(wiredep({
directory: './app/lib'
}))
.pipe(gulp.dest('./app'));
});

gulp.task('watch', function() {
[...]
gulp.watch(['./app/stylesheets/**/*.styl'], ['css', 'inject']);
gulp.watch(['./app/scripts/**/*.js', './Gulpfile.js'], ['jshint', 'inject']);
gulp.watch(['./bower.json'], ['wiredep']);
});

gulp.task('default', ['server', 'inject', 'wiredep', 'watch']);

With Gulp running in one terminal window, we can install the dependencies with Bower from another, for example, starting with those of Angular and the Bootstrap CSS framework:

$ bower install --save angular
$ bower install --save bootstrap

If we open index.html we can see that Gulp has automatically injected the AngularJS library and the files main.css and main.js that we had as styles and the Bootstrap scripts have also been injected:

<!doctype html>
<html lang="en">
<head>
[...]
<!-- bower:css -->
<link rel="stylesheet" href="lib/bootstrap/dist/css/bootstrap.css" />
<!-- endbower -->
<!-- inject:css -->
<link rel="stylesheet" href="/stylesheets/main.css">
<!-- endinject -->
</head>
<body>
[...]
<!-- bower:js -->
<script src="lib/jquery/dist/jquery.js"></script>
<script src="lib/angular/angular.js"></script>
<script src="lib/bootstrap/dist/js/bootstrap.js"></script>
<!-- endbower -->
<!-- inject:js -->
<script src="/scripts/main.js"></script>
<!-- endinject -->
</body>
</html>

If for any reason we don’t need to use one of the libraries that we have installed, we can delete them with uninstall as in the following example:

$ bower uninstall --save angular

4.3 Application Modules

All of the files that contain the functionality of our application are in the directory app/scripts.

In Angular, it is ideal to create modules for each functionality that we have in the application. If the application isn’t very large, as in the case of the example in this book, we can gather related functionalities in the same file and only separate controllers, services, directives, etc... This way we have a scalable and maintainable application.

The project that we are going to complete for this example will be a blog, with posts and comments.

It is ideal for us to receive the data from a RESTful API that returns the data in JSON format. The development of the back end is beyond the scope of this book, which is why we are going to use the JSONPlaceholder project, which provides a test API for testing and prototyping that returns JSONs of blog posts and comments.

The URLs of the API that we are going to use are:

URL

Method

/posts

POST

/posts

GET

/posts/:postId

GET

/comments

GET

/comments/:commentId

GET

/users

GET

/users/:userId

GET

The following examples are API Requests and responses from JSONPlaceholder

GET /posts/1:

{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}

GET /comments/1:

{
"postId": 1,
"id": 1,
"name": "id labore ex et quam laborum",
"email": "Eliseo@gardner.biz",
"body": "laudantium enim quasi est quidem magnam voluptate ipsam eos\ntempora quo necessitatibus\ndolor quam autem quasi\nreiciendis et nam sapiente accusantium"
}

GET /users/1:

{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere@april.biz",
"address": {
"street": "Kulas Light",
"suite": "Apt. 556",
"city": "Gwenborough",
"zipcode": "92998-3874",
"geo": {
"lat": "-37.3159",
"lng": "81.1496"
}
},
"phone": "1-770-736-8031 x56442",
"website": "hildegard.org",
"company": {
"name": "Romaguera-Crona",
"catchPhrase": "Multi-layered client-server neural-net",
"bs": "harness real-time e-markets"
}
}

4.3.1 Architecture

The file structure that we are going to use in the directory app will be the following:

app/
├── lib/
├── scripts/
| ├── services.js
| ├── controllers.js
| └── app.js
├── stylesheets/
├── views/
| ├── post-detail.tpl.html
| ├── post-list.tpl.html
| └── post-create.tpl.html
|
└── index.html

Note: The ideal, for an Angular application to be more modular, is to separate the functionalities by folders, and that each functionality has its services, controllers, routes, etc... separated from the rest. It would be something like this:

app/
├── lib/
├── modules/
| ├── users/
| | ├── module.js
| | ├── controllers.js
| | └── services.js
| ├── posts/
| | ├── module.js
| | ├── controllers.js
| | ├── services.js
| | └── views/
| | ├── post-detail.tpl.html
| | ├── post-list.tpl.html
| | └── post-create.tpl.html
| └── comments
| ├── module.js
| ├── controllers.js
| └── services.js
├── stylesheets/
|
└── index.html

Including separating the styles by functionality. But this is beyond the scope of this book, since the functionality that we are going to carry out for the example is very simple and can be written with the previous architecture.

We are going to install the following libraries that we will need, which we’ll do like always with Bower, with the flag --save-dev so that they remain saved in the bower.json file and Gulp can run the appropriate task.

$ bower install --save angular-route
$ bower install --save angular-resource

· angular-route allows us to make use of the directive $routeProvider in order to manage URLs from the browser and display one page or another to the user.

· angular-resource meanwhile, allows us to use the directive $resource which allows us to manage AJAX requests to REST resources in a simpler way and with cleaner syntax, instead of using the directives $http.get or $http.post.

To tell the HTML that we are using an Angular application, we have to put the attribute ng-app in part of our index.html; in this case we will put it in the body tag so that it includes the entire page. We will call this application “blog”:

index.html

<body ng-app="blog">
<div class="container-fluid">
<div class="row">
<aside class="col-sm-3">
<a ng-href="/new">Write Post</a>
</aside>
<section class="col-sm-9" ng-view></section>
</div>
</div>
</body>

In the above example, in addition to the attribute ng-app="blog” we have added some layout using Bootstrap classes, such as col-sm-3 and col-sm-9, which allow us to have 2 columns on the page, one of 3/12 size that can be used for information on the author of the blog, and another of 9/12 size for the content and the list of blog posts.

This last column in turn contains the attribute ng-view that tells the Angular blog application that it will load the partial views in that space, which we will manage thanks to the routing of $routeProvider later on.

4.3.2 Main

We begin with the first of the JS scripts that will give the application functionality, in this case the main file app/scripts/app.js.

In all of the JS files, in addition to using the notation "use strict"; we are going to group them by Angular modules and in turn as closures. This will help us so that the variables we use in that function only remain defined within it, and so that errors don’t appear when it is minified. The file would start like this:

(function () {
'use strict';
// Functionality goes here.
})();

Next we configure the blog module with the dependency ngRoute which we get by adding the library angular-route.

(function () {
'use strict';

angular.module('blog', ['ngRoute']);

})();

We are going to create a configuration function to tell the application which routes to listen for in the browser and which partial views to load in each case. We do this with the directive $routeProvider.

function config ($locationProvider, $routeProvider) {
$locationProvider.html5Mode(true);

$routeProvider
.when('/', {
templateUrl: 'views/post-list.tpl.html',
controller: 'PostListController',
controllerAs: 'postlist'
})
.when('/post/:postId', {
templateUrl: 'views/post-detail.tpl.html',
controller: 'PostDetailController',
controllerAs: 'postdetail'
})
.when('/new', {
templateUrl: 'views/post-create.tpl.html',
controller: 'PostCreateController',
controllerAs: 'postcreate'
});
}

The line $locationProvider.html5Mode(true) is important, since it allows URLs not to have the character # at the beginning of them, which Angular uses by default. This keeps them cleaner.

Important In order for the HTML5 mode to function correctly, you must add the tag base in your index.html document, within the headers <head> like this:

<head>
<base href="/">
...
</head>

You can find more information in this link

We have added three routes, the root or main / route, one that details a blog post post/:postId and one with the form for publishing a new post. Each one of these loads a partial view that we will create in a few moments and will be stored in the app/views folder. Each view also has an associated controller that will manage the associated functionality.

These will be PostListController, to manage the list of posts and PostDetailController to manage a specific post and PostCreateController. We will declare each of them in a separate file and module, blog.controllers, so in order to make use of them in this file, we must include it as a dependency when declaring the module, as we did with ngRoute.

The attribute controllerAs allows us to use controller variables within the HTML template without needing to use the directive $scope.

(function () {
'use strict';

angular.module('blog', ['ngRoute', 'blog.controllers']);
[...]
})

To finish with this file, we just need to associate the config function we created with the module:

angular
.module('blog')
.config(config);

The finished app/scripts/app.js file would be as follows:

(function () {
'use strict';

angular.module('blog', ['ngRoute', 'blog.controllers']);

function config ($locationProvider, $routeProvider) {
$locationProvider.html5Mode(true);

$routeProvider
.when('/', {
templateUrl: 'views/post-list.tpl.html',
controller: 'PostListController',
controllerAs: 'postlist'
})
.when('/post/:postId', {
templateUrl: 'views/post-detail.tpl.html',
controller: 'PostDetailController',
controllerAs: 'postdetail'
})
.when('/new', {
templateUrl: 'views/post-create.tpl.html',
controller: 'PostCreateController',
controllerAs: 'postcreate'
});
}

angular
.module('blog')
.config(config);

})();

4.3.3 Services

Before implementing the controller functions, we are going to create some services with the directive $resource that will allow us to make AJAX calls to the API in a simpler way. We will create three factories (that’s what they’re called), one for the Posts, another for the Comments and another for the Users. Each one of these will be associated with a REST API URL.

As in the previous file, we begin by creating a closure, and the name of the module (blog.services) to which we include the dependency ngResource contained in the library angular-resource that allows us to use the directive $resource:

(function () {
'use strict';

angular.module('blog.services', ['ngResource']);

})();

We then create 3 functions, one for each factory that will point to a URL. The server base URL we will use as a constant.

function Post ($resource, BaseUrl) {
return $resource(BaseUrl + '/posts/:postId', { postId: '@_id' });
}

function Comment ($resource, BaseUrl) {
return $resource(BaseUrl + '/comments/:commentId', { commentId: '@_id' });
}

function User ($resource, BaseUrl) {
return $resource(BaseUrl + '/users/:userId', { userId: '@_id' });
}

We associate these factories with the created module, and also create a constant BaseUrl that points to the URL of the API:

angular
.module('blog.services')
.constant('BaseUrl', 'http://jsonplaceholder.typicode.com')
.factory('Post', Post)
.factory('Comment', Comment)
.factory('User', User);

The finished app/scripts/services.js file would be:

(function () {
'use strict';

angular.module('blog.services', ['ngResource']);

function Post ($resource, BaseUrl) {
return $resource(BaseUrl + '/posts/:postId',
{ postId: '@_id' });
}

function Comment ($resource, BaseUrl) {
return $resource(BaseUrl + '/comments/:commentId',
{ commentId: '@_id' });
}

function User ($resource, BaseUrl) {
return $resource(BaseUrl + '/users/:userId',
{ userId: '@_id' });
}

angular
.module('blog.services')
.constant('BaseUrl', 'http://jsonplaceholder.typicode.com')
.factory('Post', Post)
.factory('Comment', Comment)
.factory('User', User);

})();

4.3.4 Controllers

Having already created the services, we can move on to implementing the partial view controllers and therefore the application. As always, we create an Angular module, in this case blog.controllers, with the dependency blog.services and we include it within a closure:

(function () {
'use strict';

angular.module('blog.controllers', ['blog.services']);

})();

The first controller, PostListController, is the simplest of all, the function would be like this:

function PostListController (Post) {
this.posts = Post.query();
}

What we are doing here is an AJAX call to the URL http://jsonplaceholder.typicode.com/posts and that returns the result within the variable posts. This is achieved using the service Post.

Now we turn to the detail view controller, PostDetailController. If we only want to see the contents of a specific post and its comments, we use the ID of the post that we want to show, which will provide the browser route via $routeParams the function would be like this:

function PostDetailController ($routeParams, Post, Comment) {
this.post = Post.query({ id: $routeParams.postId });
this.comments = Comment.query({ postId: $routeParams.postId });
}

But, if we want to see the information of the user that wrote the post, how do we do it? The first thing you think of is something like this:

function PostDetailController ($routeParams, Post, Comment, User) {
this.post = Post.query({ id: $routeParams.postId });
this.comments = Comment.query({ postId: $routeParams.postId });
this.user = User.query({ id: this.post.userId });
}

But our friend JavaScript isn’t sequential, it’s asynchronous, and when we run the 3rd line, this.post still contains nothing and shows us an error message, because at that time this.post.userId is undefined. How do we fix it? Using the directive $promise and in a callback function:

function PostDetailController ($routeParams, Post, Comment) {
this.post = {};
this.comments = {};
this.user = {}

var self = this; // To save the reference

Post.query({ id: $routeParams.postId })
.$promise.then(
//Success
function (data) {
self.post = data[0];
self.user = User.query({ id: self.user.userId });
},
//Error
function (error) {
console.log(error);
}
);

this.comments = Comment.query({ postId: $routeParams.postId });
}

This way, only when we have data relating to the post, can we access it to make more queries, as in this case, for the data of a user related to the post.

The next controller is for creating a new post:

function PostCreateController (Post) {
var self = this;

this.create = function() {
Post.save(self.post);
};
}

The finished file would be like this:

(function() {

angular
.module('blog.controllers', ['blog.services'])
.controller('PostListController', PostListController)
.controller('PostCreateController', PostCreateController)
.controller('PostDetailController', PostDetailController)

function PostListController (Post) {
this.posts = Post.query();
};

function PostCreateController (Post) {
var self = this;

this.create = function() {
Post.save(self.post);
};
};

function PostDetailController ($routeParams, Post, Comment) {
this.post = {};
this.comments = {};
this.user = {}

var self = this;

Post.query({ id: $routeParams.postId })
.$promise.then(
//Success
function (data) {
self.post = data[0];
self.user = User.query({ id: self.user.userId });
},
//Error
function (error) {
console.log(error);
}
);

this.comments = Comment.query({ postId: $routeParams.postId });
};

})();

Thanks to the resource Post we can use the method save() which is responsible for making a POST request to the API that we are managing. Since the API we are using is fake the POST will not be stored, but in a real API that would occur.

We add the created functions to the module blog.controllers and then we can use the variables this.posts, this.post, this.comments and this.user in our partial views, as well as collect the data that is sent in the form for creating posts. Next we will create the partial views or templates.

4.3.5 Partial views

views/post-list.tpl.html

This will be the view that shows the list of posts that the API returns to us. It is managed by the controller PostListController, which if we recall, contained the variable this.posts where they are all stored. To access that variable from the HTML we only need to indicate the alias that we gave the controller in the config function of app/scripts/app.js that was postlist, and then using point notation access the attribute posts with postlist.posts.

Since it is an array of elements, we can iterate over them using the Angular directive ng-repeat in the following manner:

<ul class="blog-post-list">
<li class="blog-post-link" ng-repeat="post in postlist.posts">
<a ng-href="/post/{{ post.id }}">{{ post.title }}</a>
</li>
</ul>

The previous code runs through the array of posts, and for each one of them, creates an <li> element in the HTML, with a link that points to the post id with post.id and the title of the post with post.title.

If we access http://localhost:8080 in a browser we will see a list of titles, all returned via the API that provides us http://jsonplaceholder.com/posts

We have also added classes to the HTML tags in order to use them later in the CSS files to add style to them.

views/post-detail.tpl.html

Now we are going to create the detail view of a specific post, where we will use the variables post, comments and user that are used by the controller PostDetailController via the alias postdetail.

First we design the element <article> where we will place the contents of the post and the user who wrote it, which are stored in the variables postdetail.post and postdetail.user:

<article class="blog-post">
<header class="blog-post-header">
<h1>{{ postdetail.post.title }}</h1>
</header>
<p class="blog-post-body">
{{ postdetail.post.body }}
</p>
<p>
Escrito por: <strong>{{ postdetail.user[0].name }}</strong>
<span class="fa fa-mail"></span> {{ postdetail.user[0].email }}
</p>
</article>

Then we add the element <aside> where the list of associated comments will be, stored in the variable postdetail.comments. Since it is an array of elements, we can use the directive ng-repeat as we did in the list of posts, to display them in the view:

<aside class="comments">
<header class="comments-header">
<h3>
<span class="fa fa-comments"></span>
Comments
</h3>
</header>
<ul class="comments-list">
<li class="comment-item" ng-repeat="comment in postdetail.comments">
<span class="fa fa-user"></span>
<span class="comment-author">{{ comment.email }}</span>
<p class="comment-body">
{{ comment.body }}
</p>
</li>
</ul>
</aside>

views/post-create.tpl.html

This will be the view that we see when we click the link Create Post; it is simply a form with the necessary fields to enter the title of the post and its contents:

<section>
<form name="createPost" role="form" ng-submit="postcreate.create()">

<fieldset class="form-group">
<input class="form-control input-lg"
type="text"
placeholder="Post title"
ng-model="postcreate.post.title">
</fieldset>
<fieldset class="form-group">
<textarea class="form-control input-lg"
placeholder="Post text"
ng-model="postcreate.post.text"></textarea>
</fieldset>
<hr>

<button class="btn btn-primary btn-lg"
type="submit" ng-disabled="!createPost.$valid">
<span class="fa fa-rocket"> Publish</span>
</button>

</form>
</section>

The classes used are part of the Bootstrap CSS Framework which we use to add style to the example.

If we look closely, both the input and the textarea have the attribute ng-model. This will allow Angular, via the controller that we previously defined for this form, to collect this data and for us to send it to the API. They will be included within the object this.post.

Another important thing we have done is disable the “Publish” button until the form is completed. We achieve this with the attribute ng-disabled="!createPost.$valid" with createPost as the name we have given the form.

The processing of this form is done using the this.create() function of the PostCreateController that we call from the attribute ng-submit.

This completes the layout and functionality of this example application with AngularJS, using factories as a model to obtain the data from an external API. Now it’s time to add some drops of CSS style (using Stylus) to finish off our app.