angular.module("fileDownload", ['ngCookies']);
<!DOCTYPE html>
<html ng-app="fileDownload">

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <link data-require="bootstrap-css@3.1.1" data-semver="3.2.0" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" />
    <script>document.write('<base href="' + document.location + '" />');</script>
    <link rel="stylesheet" href="style.css" />
    <script data-require="angular.js@1.2.x" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.17/angular.js" data-semver="1.2.17"></script>
    <script data-require="jquery@*" data-semver="2.1.1" src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.18/angular-cookies.js"></script>
    <script src="app.js"></script>
    <script src="controller.js"></script>
    <script src="service.js"></script>
  </head>

  <body ng-controller="downloadCtrl">
    <button ng-click="downloadFile()"  ng-disabled="!!downloadFileText"  ng-style="{ 'text-align': downloadFileText.indexOf('Loading') > -1 ? 'left' : 'center' }" style="width: 125px">
      {{ downloadFileText || 'Export' }}
      <i ng-show="downloadFileText === 'Loaded'" class="glyphicon glyphicon-ok-circle"></i>
      <i ng-show="downloadFileText === 'Failed'" class="glyphicon glyphicon-ban-circle"></i>
    </button>
  </body>
</html>
body {
  padding: 20px;
}

button {
  background-color: #fff;
  border-color: #ccc;
  display: inline-block;
  padding: 6px 12px;
  margin-bottom: 0;
  font-size: 14px;
  font-weight: normal;
  line-height: 1.428571429;
  text-align: center;
  white-space: nowrap;
  vertical-align: middle;
  cursor: pointer;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  -o-user-select: none;
  user-select: none;
  background-image: none;
  border-radius: 4px;
}

.glyphicon-ban-circle {
  color: red;
  font-size: 12px;
}

.glyphicon-ok-circle {
  color: green;
  font-size: 12px;
}
angular.module("fileDownload").controller("downloadCtrl", function($scope, $timeout, $q, downloadService) {
  $scope.downloadFile = function() {
    var params = {};
    var loadingText = 'Loading Data';
    var options = ['.', '..', '...'];

    $scope.downloadFileText = loadingText + options[0];
    var promise = downloadService.validateBeforeDownload(params).then(null, function(reason) {
      alert(reason);
      // you can also throw the error
      // throw reason;
      return $q.reject(reason);
    }).then(downloadService.downloadFile).then(function() {
      $scope.downloadFileText = 'Loaded';
    }, function() {
      $scope.downloadFileText = 'Failed';
    }, function(i) {
      i = (i + 1) % 3;
      $scope.downloadFileText = loadingText + options[i];
    });

    promise.
    finally(function() {
      $timeout(function() {
        delete $scope.downloadFileText;
      }, 2000);
    });
  };
});
angular.module("fileDownload").factory("downloadService", function($interval, $timeout, $q, $cookieStore) {

  var generateIframeDownload = function() {
    var iframe = document.createElement('iframe');
    $cookieStore.put('download_file', 'true');

    iframe.src = '/myserver/dowload';
    iframe.style.display = "none";
    document.body.appendChild(iframe);
  }

  var manageIframeProgress = function() {
    var defer = $q.defer();

    // notify that the download is in progress every half a second / do this for a maximum of 50 intervals 
    var promise = $interval(function() {
      if (!$cookieStore.get('download_file')) {
        $interval.cancel(promise);
      }
    }, 500, 50);

    promise.then(defer.reject, defer.resolve, defer.notify);

    promise.finally(function() {
      $cookieStore.remove('download_file');
      document.body.removeChild(iframe);
    });

    return defer.promise;
  }

  return {
    validateBeforeDownload: function(config) {
      var defer = $q.defer();

      // notify that the download is in progress every half a second
      $interval(function(i) {
        defer.notify(i);
      }, 500);

      //mock response from server - this would typicaly be a $http request and response
      $timeout(function() {
        // in case of error: 
        //defer.reject("this file can not be dowloaded");
        defer.resolve(config);
      }, 3000);

      return defer.promise;
    },
    downloadFile: function(config) {

      generateIframeDownload();
      var promise = manageIframeProgress();

      //mock response from server - this would be automaticlly triggered by the file download compeletion
      $timeout(function() {
        $cookieStore.remove('download_file');
      }, 3000);

      return promise;
    }
  }
});