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: .transformRequest
14: .push(function(data) {
15: requestNotificationProvider
16: .fireRequestStarted(data);
17: return data;
18: });
19:
20: $httpProvider
21: .defaults
22: .transformResponse
23: .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: requestNotification
11: .subscribeOnRequestStarted(function() {
12: // show the spinner!
13: element.show();
14: });
15:
16: requestNotification
17: .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