Reputation: 4470
In my Angular app, I have implemented this directive (code below) that basically allows me to show an element of my choosing whenever Angular detects an ajax request.
However, for slightly better usability, I would like to show the spinner only after some time has passed (say, 100 or 200 miliseconds) since the beginning of the request, to avoid those unnecessary split-second displays on every single request.
What would be the best way to implement such a thing? I'm having trouble getting setTimeout
to play nicely within the if
block because the element will never get hidden again, even if I no longer have a pending request.
.directive('loading', ['$http' ,function ($http)
{
return {
restrict: 'A',
link: function (scope, elm, attrs)
{
scope.isLoading = function () {
return $http.pendingRequests.length > 0;
};
scope.$watch(scope.isLoading, function (v)
{
if(v){
elm.show();
} else {
elm.hide();
}
});
}
};
}]);
Upvotes: 2
Views: 3653
Reputation: 16979
Sounds like you can leverage interceptors and bind to a root variable instead of a directive to show your element for pending ajax requests (after the time threshold is met). Observe the following possibility...
app.factory('HttpInterceptor', ['$rootScope', '$q', '$timeout', function ($rootScope, $q, $timeout) {
return {
'request': function (config) {
$timeout(function() {
$rootScope.isLoading = true; // loading after 200ms
}, 200);
return config || $q.when(config);
},
'requestError': function (rejection) {
/*...*/
return $q.reject(rejection);
},
'response': function (response) {
$rootScope.isLoading = false; // done loading
return response || $q.when(response);
},
'responseError': function (rejection) {
/*...*/
return $q.reject(rejection);
}
};
}]);
// register interceptor
app.config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push('HttpInterceptor');
/*...*/
}]);
<!-- plain element with binding -->
<div class="whatever" ng-show="isLoading"></div>
JSFiddle Link - working demo
Upvotes: 2
Reputation: 21475
For a single, globally-available loading indicator, an http interceptor is probably a better strategy. But assuming you want to attach this to individual elements separately, try something like this:
.directive('loading', ['$http', '$timeout', function($http, $timeout) {
return {
restrict: 'A',
link: function(scope, elm, attrs) {
scope.isLoading = function() {
return $http.pendingRequests.length > 0;
};
if (scope.isLoading) {
elm.hide(); // hide the loading indicator to begin with
// wait 300ms before setting the watcher:
$timeout(function() {
var watcher = scope.$watch(scope.isLoading, function(v) {
if (v) {
elm.show();
} else {
elm.hide();
watcher(); // don't forget to clear $watches when you don't need them anymore!
}
});
}, 300);
} else {
// No pending requests on link; hide the element and stop
elm.hide();
}
}
};
}]);
(You should probably also include a $destroy
block on the directive to call watcher()
, in case the directive goes out of scope while http requests are still pending.)
Upvotes: 2