Angular, show loading when any resource is in pending

Daredzik picture Daredzik · Oct 14, 2014 · Viewed 13.9k times · Source

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 ?

Answer

Mikko Viitala picture Mikko Viitala · Oct 14, 2014

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