Introduction
When developing Angular apps, sometimes the request can take some time to process. What kind of indication do you give the user that the request is still loading? If you develop a custom solution, how do you make it reusable across all your HTTP requests?
We will take this problem by using the angular-loading-bar
library. Small progress bar on top of the page, similar to what you might've seen YouTube do recently.
In addition, we will use the ui-router library
. This will allow us to create a flexible routing system. I will go in more detail later in this article.
Finally, we will build a decent angular js app with some new concepts like one-time binding and controller as. We will also make our application modular.
Prerequisites
- AngularJs - I won't go over simple concepts like modules, controllers, services, and directives. Those concepts are not the goal of this project, but having some knowledge will help.
- JavaScript/HTML - Duh
What We're Building: A Simple Github App (Dun! Dun! dun!)
To cover these examples, we will build a very simple GitHub application that will search for repos for a user and let the user view some repo details. This is how the application looks like.
This is a very simple application, primarily built to cover some simple concepts.
Background
Before going over the code, I want to go over some concepts.
The IFFE Pattern
I've used this pattern along with 'use strict
' to keep things clean, modular, and separate from the global scope. IIFE is an anonymous function that executes on page load. The code is wrapped within the anonymous function to avoid any conflicts with the global scope. Here's an example:
(function () {
'use strict';
}());
AngularJs One-time Binding {{::$scope.title}}
If you were not aware, this is a new feature in v1.3. The "::
" allows you to do one-time binding. You should use this with caution. You don't want to mistakenly one-time bind a property you might expect to update later. On the other hand, use this where possible, where the bound data is practically static, like a million row table. (Hint: avoid that!)
AngularJs Controller "as"
This is a feature introduced in v1.2. This allows you to use to use the "this
" scoped object from the controller within the template HTML, instead of injecting $scope
. You can read more here. The controllers in this project use this syntax, instead of the $scope
.
angular-loading-bar
This is a cool project that I really like. After you inject the "angular-loading-bar
" into your app, it automatically tracks $http
and $resource
requests and shows the loading bar. The loading bar automatically shows, you do not need to do anything!
This is the project page, and you can get the scripts from here or MaxCDN.
var app = angular.module("gitHubApp", ["angular-loading-bar"]);
ui-router
This is a state based routing system for Angular. Unlike the ng-router
, this is not based on URLs, but rather the state of the app. You can define states like: productEdit
, productView
, productsAll
. For each state, you can specify a controller, template, resolve, abstract, and some other properties. UI-router also supports partial views using partial states. This concept is out of the scope of this article. You can read more about partial states here.
Defining Routes
This code is commented to make sense.
var app = angular.module("gitHubApp", ["ui.router"]);
app.config(
[
"$stateProvider",
function ($stateProvider) {
$stateProvider
.state("search", {
url: "/",
templateUrl: "app/github/views/searchView.html",
controller: "SearchController as vm"
})
.state("repoDetail", {
url: "/repo/:username/:repoName",
templateUrl: "app/github/views/repoDetailView.html",
controller: "RepoDetailController as vm",
resolve: {
gitHubService: "gitHubService",
repoDetail: function (gitHubService, $stateParams) {
return gitHubService.getRepoInfo
($stateParams.username, $stateParams.repoName);
}
}
});
}
]);
Below in the article, you can find the code for the Repository View Controller and see how the resolve injects the repoDetail
.
Loading a State From the Controller
From the controller, you can load a new state by injecting the $state
and then calling $state(stateName, {optionStates});
Example:
angular.module("gitHubApp").controller("SearchController", ["$state", searchController]);
function searchController($state) {
var vm = this;
vm.viewDetail = function() {
$state.go("repoDetail", {username: vm.username, repoName: vm.repoName});
}
}
Loading a State From the View
You can use the ui-sref
or the data-ui-sref
to specify the state and additional parameters.
<a ui-sref="repoDetail({username: result.owner.login, repoName: result.name})">
{{::result.full_name}}</a>
<a ui-sref="search">go back</a>
Using the Code
You have already seen the code for the app in the ui-router
and the angular-loading-bar
sections above. Here, I will focus on the other parts.
Organization of the Project
This is how the project is organized:
I feel it's easier to keep modules within the app directory and group the relevant js and view files. This makes it easier to find the relevant view and controller files. Common services, directives, filters, etc. are all within the common directory.
As for the naming convention, it is *Controller.js and *View.html. Example: searchController.js and searchView.html.
The GitHub Service
I ended up creating a separate service for the github API calls. I didn't want to overcomplicate things for this simple example. The code is very self-explanatory.
(function () {
'use strict';
angular.module("gitHubApp").service("gitHubService", ["$http", gitHubService]);
function gitHubService($http) {
var getReposByUser = function (query) {
return $http.get("https://api.github.com/users/" +
encodeURIComponent(query) + "/repos");
}
var getRepoInfo = function (username, repoName) {
return $http.get("https://api.github.com/repos/" +
encodeURIComponent(username) + "/" + encodeURIComponent(repoName));
}
return {
getReposByUser: getReposByUser,
getRepoInfo: getRepoInfo
};
}
}());
The Search Controller
This search controller basically handles the search web service call to get the github results.
(function () {
'use strict';
angular.module("gitHubApp").controller
("SearchController", ["gitHubService", searchController]);
function searchController(gitHubService) {
var vm = this;
vm.search = function () {
if (!vm.query || vm.query.length == 0) {
alert('Please specify a search query');
return;
}
gitHubService.getReposByUser(vm.query).then(function (response) {
vm.results = response.data;
});
}
}
}());
The Search View
We add a search UI and results table. Search input binds to ng-model="vm.query"
. The results table only shows if there are valid results. We also have a few validation div
s to show possible errors.
<!--
<div>
<label for="inputUsername">Username:</label>
<!--
<input type="text" class="form-control" placeholder="Username"
id="inputUsername" ng-model="vm.query" required>
<!--
<button class="btn btn-primary" type="button" ng-click="vm.search();">Search</button>
</div>
<!--
<div class="alert alert-warning" ng-if="vm.query && vm.results.message">
No results found. Please try again.
</div>
<div class="alert alert-warning" ng-if="vm.results && vm.results.length == 0">
No repos found. Please try again.
</div>
<!--
<table class="table table-striped" ng-if="vm.results">
<thead>
<tr>
<th>Id</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="result in vm.results">
<!--
<td>{{::result.id}}</td>
</tr>
</tbody>
</table>
The Repository Detail Controller
This is much more interesting. :) If you remember the ui-router
's state "repoDetail
", you will remember that we used the resolve
property to return a repoDetail
object. If you don't remember, please refer to ui-router
above. Here's the code:
(function () {
'use strict';
angular.module("gitHubApp").controller("RepoDetailController",
["repoDetail", repoDetailController]);
function repoDetailController(repoDetail) {
var vm = this;
vm.repo = repoDetail.data;
}
}());
FYI: The controller and the template do not load until the resolve function finishes. The angular-loading-bar
, however, displays the progress bar indicating the content is loading, which is super neat.
The Repository Detail View
Nothing too interesting is happening here to share, other than the back button, which uses the ui-sref
.
<!-- ui-sref="stateName" -->
<a class="btn btn-primary" ui-sref="search" style="margin-top: 10px;">go back</a>
The Final Application
The final application is built on top of BootStrap and contains a few more details. Again, this is a concept application. The goal was to keep it simple and cover different concepts.
Points of Interest
I think angular-loading-bar
with the combination of ui-router
's resolve
property works beautifully! It works just like the browser, how user would expect. When a request is processing, you get a progress bar, and when content loads, you get the actual view.
The ui-router
library is also powerful. The concept of states makes application development and management very easy and straightforward.
Last but not least, the one-time binding with {::property}
is amazing. This one I've been looking forward to for a very long time. (Even though I got into Angular only last month, I had kept an eye out on the performance enhancements for the binding).
History
- Release 2/7/2014: Added simple GitHub application that pulls repos for a user and shows repo details. The application also demonstrates the
ui-router
and angular-loading-bar
components.