Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular, show loading when any resource is in pending

I already write a code to display a loader div, when any resources is in pending, no matter it's getting via $http.get or routing \ ng-view. I wan't only information if i'm going bad...

flowHandler service:

app.service('flowHandler', function(){
    var count = 0;
    this.init = function() { count++ };
    this.end = function() { count-- };
    this.take = function() { return count };
});

The MainCTRL append into <body ng-controller="MainCTRL">

app.controller("MainCTRL", function($scope, flowHandler){
    var _this = this;
    $scope.pageTitle = "MainCTRL";
    $scope.menu = [];
    $scope.loader = flowHandler.take();

    $scope.$on("$routeChangeStart", function (event, next, current) {
        flowHandler.init();
    });

    $scope.$on("$routeChangeSuccess", function (event, next, current) {
        flowHandler.end();
    });

    updateLoader = function () {
        $scope.$apply(function(){
            $scope.loader = flowHandler.take();
        });

    };

    setInterval(updateLoader, 100);

});

And some test controller when getting a data via $http.get:

app.controller("BodyCTRL", function($scope, $routeParams, $http, flowHandler){
    var _this = this;
    $scope.test = "git";

    flowHandler.init();
    $http.get('api/menu.php').then(function(data) {
        flowHandler.end();
        $scope.$parent.menu = data.data;

    },function(error){flowHandler.end();});
});

now, I already inject flowHandler service to any controller, and init or end a flow.

It's good idea or its so freak bad ?

Any advice ? How you do it ?

like image 468
Daredzik Avatar asked Oct 14 '14 17:10

Daredzik


4 Answers

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

imgur

...and other for all-complete. imgur

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

like image 127
Mikko Viitala Avatar answered Oct 31 '22 05:10

Mikko Viitala


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>
like image 35
Elton Hoffmann Avatar answered Oct 31 '22 05:10

Elton Hoffmann


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 :)

like image 2
John Avatar answered Oct 31 '22 04:10

John


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

like image 1
Toolkit Avatar answered Oct 31 '22 04:10

Toolkit