Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

AngularJS: Routing with Loading Bar

4.96/5 (6 votes)
10 Feb 2015CPOL5 min read 43.5K   566  
An angular application that consumes GitHub, primarily to demonstrate the use of angular-loading-bar and ui-router to give better feedback during longer requests.

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

  1. 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.
  2. 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:

JavaScript
(function () {
    'use strict';
    // Any code you execute here gets executed upon script load.
}());

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.

JavaScript
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.

JavaScript
var app = angular.module("gitHubApp", ["ui.router"]);
app.config(
[
    "$stateProvider",
        function ($stateProvider) {
            $stateProvider
                // The search state : loads the search page
                .state("search", {
                    // The Url to set for this state
                    url: "/", 
                    // The template to load
                    templateUrl: "app/github/views/searchView.html",
                    // Set the controller using the "as" feature released in v1.2.
                    controller: "SearchController as vm"
                })
                // Repository Detail state
                .state("repoDetail", {
                    // Set the url and accept two query string parameters.
                    url: "/repo/:username/:repoName",
                    templateUrl: "app/github/views/repoDetailView.html",
                    controller: "RepoDetailController as vm",
                    // Resolve : This is called before the template or controller is loaded.
                    // This is perfect since it makes the $http request 
                    // and the loading bar shows
                    // but the template loads after our data loads.
                    resolve: {
                        // DI, inject this service into repoDetail() below
                        gitHubService: "gitHubService",
                        // repoDetail is another DI method, which will be resolved 
                        // and injected into our controller.
                        // $stateParams is automatically injected into the function
                        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:

JavaScript
// Create out controller and inject the $state
angular.module("gitHubApp").controller("SearchController", ["$state", searchController]);
function searchController($state) {
    var vm = this;
    vm.viewDetail = function() {
        // Specify the stateName and pass in the parameters required by that state.
        $state.go("repoDetail", {username: vm.username, repoName: vm.repoName});
        // If no paramaters, then simple $state.go("repoDetail");
    }
}

Loading a State From the View

You can use the ui-sref or the data-ui-sref to specify the state and additional parameters.

JavaScript
<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.

JavaScript
(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.

JavaScript
(function () {
    'use strict';

    angular.module("gitHubApp").controller
                  ("SearchController", ["gitHubService", searchController]);
    function searchController(gitHubService) {
        var vm = this;
        // Search function called when search button is clicked
        vm.search = function () {
            // Check if our vm.query is specified.
            if (!vm.query || vm.query.length == 0) {
                alert('Please specify a search query');
                return;
            }
            
            // Make call to webservice to get repos for the user.
            gitHubService.getReposByUser(vm.query).then(function (response) {
                // We can handle error using response.status != 200, 
                // or using the sucess() error() callbacks, instead of then()
                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 divs to show possible errors.

HTML
<!-- Since we use Controller as vm, the vm property automatically gets injected here. -->
<div>
    <label for="inputUsername">Username:</label>
    <!-- The Search Input : binds to vm.query -->
    <input type="text" class="form-control" placeholder="Username" 
                       id="inputUsername" ng-model="vm.query" required>
    <!-- Search button fires the search using ng-click -->
    <button class="btn btn-primary" type="button" ng-click="vm.search();">Search</button>
</div>

<!-- Possibly invalid search query. -->
<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>

<!-- Go through our results, if any, and display them -->
<table class="table table-striped" ng-if="vm.results">
    <thead>
        <tr>
            <th>Id</th>
        </tr>
    </thead>
    <tbody>
        <tr ng-repeat="result in vm.results">
            <!-- "::" is one time binding released in AngularJs 1.3-->
            <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:

JavaScript
(function () {
    'use strict';

    angular.module("gitHubApp").controller("RepoDetailController", 
                                ["repoDetail", repoDetailController]);
    // repoDetail automatically resolved and got injected here from the state.
    function repoDetailController(repoDetail) {
        var vm = this;
        vm.repo = repoDetail.data; // Use statusText != 200 to catch errors
    }

}());

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.

JavaScript
<!-- 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

  1. 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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)