5

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 ?

sma
  • 9,449
  • 8
  • 51
  • 80
Daredzik
  • 422
  • 2
  • 9
  • 21
  • Have a look at the source code of [angular loading bar](https://github.com/chieffancypants/angular-loading-bar). Copy the core things or just use it. (Sorry, I didn't read your question, but why would you want to invent the wheel once again?) – hgoebl Oct 14 '14 at 17:34
  • I want show wheel or div (loading), to inform about some data is in pending $get/$post. – Daredzik Oct 14 '14 at 17:37
  • plunker maybe? is this "what you want" or are you open to other ways to implement this functionality? – Mikko Viitala Oct 14 '14 at 17:51
  • Yes, at this moment is "what I want" but i would like get any advice if this can be do better or if it's bad idea to do it with my solution – Daredzik Oct 14 '14 at 18:08
  • will you end up hiding loading when you have multiple requests and one gets first? – Toolkit Jan 27 '16 at 08:05

5 Answers5

4

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

Mikko Viitala
  • 8,344
  • 4
  • 37
  • 62
3

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>
Elton Hoffmann
  • 110
  • 1
  • 9
  • I dont want to display loader for specific pending request but for all pendings ;) if any wait... even when angular set a promise. – Daredzik Oct 14 '14 at 18:43
  • You could check for `$http.pendingRequests.length` when displaying the loader. But yes, this will only return true for requests for the server. Angular promises will not count. – Elton Hoffmann Oct 14 '14 at 19:11
2

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

John
  • 6,503
  • 3
  • 37
  • 58
1

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

Community
  • 1
  • 1
Toolkit
  • 10,779
  • 8
  • 59
  • 68
0

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

CaughtOnNet
  • 360
  • 2
  • 7