
UPDATE: Does not work in 1.3.0. Use Interceptors instead. See Angular Request Count & Loading Widget (ng 1.3.0).
When using angular JS, it would be nice to inform the user that some content is being loaded or that there are some pending requests. I found some solutions of how to show this information to user globally. But in most of these cases the code they used wasn’t clean.
One of the problem was using $rootScope to $broadcast an event informing that the loading has started. Imagine the tons of listeners being notified.
Another problem was the use of Request/Response interceptors. Sometimes this was a unreliable solution for a reason I was unable to find.
The third problem was usage of pendingRequests property of $http service. The documentation states clearly that it should be used for debugging purposes only.
Therefore, I decided writing my very first provider to cope with these problems. I register this provider during config phase of the application as a Request/Response transformer. Every single request/response goes through this function.
This is how my requestNotification provider looks like:
1: angular.module('myApp.services').provider(
2: 'requestNotification', function() {
3: // This is where we keep subscribed listeners
4: var onRequestStartedListeners = [];
5: var onRequestEndedListeners = [];
6: 7: // This is a utility to easily increment the request count
8: var count = 0;
9: var requestCounter = {
10: increment: function() {
11: count++; 12: },13: decrement: function() {
14: if (count > 0)
15: count--; 16: },17: getCount: function() {
18: return count;
19: } 20: };21: // Subscribe to be notified when request starts
22: this.subscribeOnRequestStarted = function(listener) {
23: onRequestStartedListeners.push(listener); 24: }; 25: 26: // Tell the provider, that the request has started.
27: this.fireRequestStarted = function(request) {
28: // Increment the request count
29: requestCounter.increment(); 30: //run each subscribed listener
31: angular.forEach(onRequestStartedListeners, function(listener) {
32: // call the listener with request argument
33: listener(request); 34: }); 35: return request;
36: }; 37: 38: // this is a complete analogy to the Request START
39: this.subscribeOnRequestEnded = function(listener) {
40: onRequestEndedListeners.push(listener); 41: }; 42: 43: 44: this.fireRequestEnded = function() {
45: requestCounter.decrement();46: var passedArgs = arguments;
47: angular.forEach(onRequestEndedListeners, function(listener) {
48: listener.apply(this, passedArgs);
49: });50: return arguments[0];
51: }; 52: 53: this.getRequestCount = requestCounter.getCount;
54: 55: //This will be returned as a service
56: this.$get = function() {
57: var that = this;
58: // just pass all the functions
59: return {
60: subscribeOnRequestStarted: that.subscribeOnRequestStarted, 61: subscribeOnRequestEnded: that.subscribeOnRequestEnded, 62: fireRequestEnded: that.fireRequestEnded, 63: fireRequestStarted: that.fireRequestStarted, 64: getRequestCount: that.getRequestCount 65: }; 66: }; 67: } 68: );The provider methods (this.someFunction) can be accessed during the configuration phase of the application. So I add the request/response transformers:
1: angular.module('myApp', [
2: 'ngRoute',
3: 'myApp.filters',
4: 'myApp.services',
5: 'myApp.directives',
6: 'myApp.controllers'
7: ])8: .config(function(
9: $httpProvider, 10: requestNotificationProvider) { 11: $httpProvider 12: .defaults 13: .transformRequest14: .push(function(data) {
15: requestNotificationProvider 16: .fireRequestStarted(data);17: return data;
18: }); 19: 20: $httpProvider 21: .defaults 22: .transformResponse23: .push(function(data) {
24: requestNotificationProvider 25: .fireRequestEnded(data);26: return data;
27: }); 28: });Finally, I create a directive which listens onRequestStarted/Ended. It shows/hides the element with the directive accordingly. You can place a nice animated gif to indicate a pending request.
1: angular.module('myApp.directives')
2: .directive('loadingWidget', function (requestNotification) {
3: return {
4: restrict: "AC",
5: link: function (scope, element) {
6: // hide the element initially
7: element.hide(); 8: 9: //subscribe to listen when a request starts
10: requestNotification11: .subscribeOnRequestStarted(function() {
12: // show the spinner!
13: element.show(); 14: }); 15: 16: requestNotification17: .subscribeOnRequestEnded(function() {
18: // hide the spinner if there are no more pending requests
19: if (requestNotification.getRequestCount() === 0)
20: element.hide(); 21: }); 22: } 23: }; 24: });Note that requestNotificationProvider has become a service to use by the directive, thus its injected as requestNotification. We can access all the methods of the object returned by the $get function.
Demo
See the attached JSFiddle for a demonstration of how it works.
Feel free to use the code in your projects. If you liked this post, like & share it. Or give me a Flattr.
And how do you do it? See a bug in my code? Do you have a different approach? Leave a comment!
No comments:
Post a Comment