How to build a UX-friendly progress indicator with AngularJS

Demonstrates building an animated progress indicator using AngularJS, and follows user experience (UX) recommendations for visibility of system status. For a more detailed walk through, check out http://www.artermobilize.com/blog/2015/08/18/how-to-build-a-ux-friendly-progress-indicator-with-angularjs/

HTML

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
<script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
<div class="indicator-overlay">
    <div class="fa fa-cog fa-spin fa-2x indicator-animation"></div>
</div>
<div ng-app="MyApp" ng-controller="MyController">
    <button ng-click="doAsyncStuff()">Click to do something</button>
</div>

CSS

button {
    font-size: 1em;
    padding: 0.5em;
}
div.indicator-overlay {
    height: 100%;
    width: 100%;
    position: absolute;
    z-index: 1000;
    background-color: rgba(200, 200, 200, 0.75);
    margin: -1rem;
    font-size: 4em;
    text-align: center;
    display: none;
}
div.indicator-animation {
    margin: 0 auto;
    width: 100%;
    margin-top: 10%;
}

JavaScript

var myApp = angular.module('MyApp', []);

myApp.controller('MyController', function ($scope, $http) {
    $scope.doAsyncStuff = function () {
         $http.post('/echo/json');
	}
});

myApp.factory("busyIndicator", function () {
    return {
        timeout: null,
        delayMilliseconds: 1000,
        indicatorIsVisible: false,
        show: function () {
            if (!this.indicatorIsVisible) {
                this.indicatorIsVisible = true;
                this.timeout = setTimeout(
                    function() {
                        $(".indicator-overlay").show();
                    },
                    this.delayMilliseconds);
            }
        },
        hide: function () {
            clearTimeout(this.timeout);
            this.indicatorIsVisible = false;
            setTimeout(
                function() {
                    $(".indicator-overlay").hide();
                }, this.delayMilliseconds);
        }
    }
});
 
myApp.factory('requestBusyIndicator', ['$q', 'busyIndicator', '$timeout', function ($q, busyIndicator, $timeout) {
    var requestIndicator = {
        request: function (config) {
            busyIndicator.show();
            return config;
        },
        response: function (response) {
            //Simulate 3 second response time by deferring the response with a timeout
            var deferred = $q.defer();
            $timeout(function () {
                busyIndicator.hide();
                deferred.resolve(response);
            }, 1100);
            return deferred.promise;
        },
        requestError: function (rejection) {
            busyIndicator.hide();
            return $q.reject(rejection);
        },
        responseError: function (rejection) {
            busyIndicator.hide();
            return $q.reject(rejection);
        }
    };
    return requestIndicator;
}]);

myApp.config(['$httpProvider',
  function ($httpProvider) {
      $httpProvider.interceptors.push('requestBusyIndicator');
}]);