Angular, show loading when any resource is in pending

孤者浪人 提交于 2019-12-04 08:11:46

I suggest you to take a look at $http's pendingRequest propertie

https://docs.angularjs.org/api/ng/service/$http

As the name says, its an array of requests still pending. So you can iterate this array watching for an specific URL and return true if it is still pending. Then you could have a div showing a loading bar with a ng-show attribute that watches this function

I would also encapsulate this requests in a Factory or Service so my code would look like this:

//Service that handles requests
angular.module('myApp')
    .factory('MyService', ['$http', function($http){

  var Service = {};

  Service.requestingSomeURL = function(){
    for (var i = http.pendingRequests.length - 1; i >= 0; i--) {
      if($http.pendingRequests[i].url === ('/someURL')) return true;
    }
    return false;
  }

  return Service;
}]);


//Controller
angular.module('myApp')
    .controller('MainCtrl', ['$scope', 'MyService', function($scope, MyService){

  $scope.pendingRequests = function(){
    return MyService.requestingSomeURL();
  }
}]);

And the HTML would be like

<div ng-show="pendingRequests()">
  <div ng-include="'views/includes/loading.html'"></div>
</div>

You could easily implement something neat using e.g. any of Bootstrap's progressbars.

Let's say all your services returns promises.

// userService ($q)
app.factory('userService', function ($q) {
  var user = {};
  user.getUser = function () {
    return $q.when("meh");
  };
  return user;
});

// roleService ($resource)
// not really a promise but you can access it using $promise, close-enough :)
app.factory('roleService', function ($resource) {
  return $resource('role.json', {}, { 
    query: { method: 'GET' }
  });
});

// ipService ($http)
app.factory('ipService', function ($http) {
  return {
    get: function () { 
      return $http.get('http://www.telize.com/jsonip');
    }
  };
});

Then you could apply $scope variable (let's say "loading") in your controller, that is changed when all your chained promises are resolved.

app.controller('MainCtrl', function ($scope, userService, roleService, ipService) {

  _.extend($scope, {
    loading: false,
    data: { user: null, role: null, ip: null}
  });

  // Initiliaze scope data
  function initialize() {
    // signal we are retrieving data
    $scope.loading = true;

    // get user
    userService.getUser().then(function (data) {
      $scope.data.user = data;
    // then apply role
    }).then(roleService.query().$promise.then(function (data) {
      $scope.data.role = data.role;
    // and get user's ip
    }).then(ipService.get).then(function (response) {
      $scope.data.ip = response.data.ip;
    // signal load complete
    }).finally(function () {
      $scope.loading = false;
    }));
  }

  initialize();
  $scope.refresh = function () {
    initialize();
  };
});

Then your template could look like.

<body ng-controller="MainCtrl">
<h3>Loading indicator example, using promises</h3>

<div ng-show="loading" class="progress">
  <div class="progress-bar progress-bar-striped active" style="width: 100%">      
      Loading, please wait...
  </div>
</div>

<div ng-show="!loading">
  <div>User: {{ data.user }}, {{ data.role }}</div>
  <div>IP: {{ data.ip }}</div>
  <br>
  <button class="button" ng-click="refresh();">Refresh</button>
</div>

This gives you two "states", one for loading...

...and other for all-complete.

Of course this is not a "real world example" but maybe something to consider. You could also refactor this "loading bar" into it's own directive, which you could then use easily in templates, e.g.

//Usage: <loading-indicator is-loading="{{ loading }}"></loading-indicator>

/* loading indicator */
app.directive('loadingIndicator', function () {
  return {
    restrict: 'E',
    scope: {
      isLoading: '@'
    },
    link: function (scope) {
      scope.$watch('isLoading', function (val) {
          scope.isLoading = val;
      });
    },
    template: '<div ng-show="isLoading" class="progress">' +
              '  <div class="progress-bar progress-bar-striped active" style="width: 100%">' +
              '        Loading, please wait...' +
              '    </div>' +
              '</div>'
  };
});

 

Related plunker here http://plnkr.co/edit/yMswXU

I'd check out this project:

http://chieffancypants.github.io/angular-loading-bar/

It auto injects itself to watch $http calls and will display whenever they are happening. If you don't want to use it, you can at least look at its code to see how it works.

Its very simple and very useful :)

I used a simpler approach:

var controllers = angular.module('Controllers', []);

controllers.controller('ProjectListCtrl', [ '$scope', 'Project',
    function($scope, Project) {
        $scope.projects_loading = true;
        $scope.projects = Project.query(function() {
            $scope.projects_loading = false;
        });
}]);

Where Project is a resource:

var Services = angular.module('Services', [ 'ngResource' ]);
Services.factory('Project', [ '$resource', function($resource) {
    return $resource('../service/projects/:projectId.json', {}, {
        query : {
            method : 'GET',
            params : {
                projectId : '@id'
            },
            isArray : true
        }
    });
} ]);

And on the page I just included:

 <a ng-show="projects_loading">Loading...</a>
 <a ng-show="!projects_loading" ng-repeat="project in projects">
      {{project.name}}
 </a>

I guess, this way, there is no need to override the $promise of the resource

Toolkit

I used a base controller approach and it seems most simple from what i saw so far. Create a base controller:

angular.module('app')
.controller('BaseGenericCtrl', function ($http, $scope) {
    $scope.$watch(function () {
        return $http.pendingRequests.length;
    }, function () {
        var requestLength = $http.pendingRequests.length;
        if (requestLength > 0)
            $scope.loading = true;
        else
            $scope.loading = false;
    });
});

Inject it into a controller

   angular.extend(vm, $controller('BaseGenericCtrl', { $scope: $scope }));

I am actually also using error handling and adding authorization header using intercepting $httpProvider similar to this, and in this case you can use loading on rootScope

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!