Saturday 18 October 2014

Angular Request Count & Loading Widget (ng 1.3.0)

I my older post (AngularJS Loading Widget) I have described how to count requests and keep track of their count to be able to notify user when something gets loaded. In the new version 1.3.0 of Angular the response transformer is not called when the request fails or returns empty response. I have updated to Interceptors. I’ve avoided using them for this purpose because I was not getting constant results with them. But now I don’t know of other options. Here’s the updated code of the .config phase. Please let me know if you come across any issues. Working JSFiddle demo. If you want to know how this works, read the older post.

angular.module('myApp', [
'myApp.services',
'myApp.directives',
'myApp.controllers'])
.config(function (
$httpProvider,
requestNotificationProvider) {
$httpProvider.interceptors.push(function ($q) {
return {
request: function (config) {
requestNotificationProvider.fireRequestStarted();
return config;
},
response: function (response) {
requestNotificationProvider.fireRequestEnded();
return response;
},

responseError: function (rejection)
{
requestNotificationProvider.fireRequestEnded();
return $q.reject(rejection);
}
}
});
});

Friday 17 January 2014

AngularJS and Bootstrap form-group (control-group) error highlighting–globally

bootstrap_errorIf you use angularJS with Bootstrap, you would probably like to use bootstrap error highlighting using form-group (ex control-group). You could use ng-class directive with an expression to add the class on every form-group you use in your application. Or you could create a directive to add this feature on one place. In this post, I’ll explain how to do it.

Let’s start with an simple example. (Most advanced is at the end of the article.) In this simple JSFiddle you can see two directives which add the functionality. First is the form-group directive:

app.directive("formGroup", function () {
return {
restrict: "C",
link: function ($scope, element, attributes) {
var errorList = {};
$scope.$on("inputError", function () {
element.addClass("has-error");
});
$scope.$on("inputValid", function () {
element.removeClass("has-error");
});
},
//own scope, so emitted messages don't get mixed up
scope: true
};

It simply listens in its own scope to two events: “inputError” on which it adds has-error class to the formGroup and inputValid on which it removes the same class. So whenever you add a form group, the directive listens on the scope to messages.


Here is how and when I send them. I use another directive for that.

app.directive("input", function () {
return {
restrict: "E",
//require the access to the controller of ngModel
//its injected as the 4th parameter of link function
require: "?ngModel",
link: function ($scope, element, attributes, modelController) {
if (!modelController)
return;
// Watch the validity of the input
$scope.$watch(function () {
return modelController.$invalid;
}, function () {
// $emit messages to the control group
if (modelController.$invalid) $scope.$emit("inputError");
else $scope.$emit("inputValid");
});
}
};
});

It’s name is input. I suggest you make a function first and use it as input, textarea and selectbox directive. That way you can achieve same behaviour on more controls. The directive itself monitors the validity of the model connected to the input–if there is ngModel directive present on the same element. When there’s a change of the validity, it emits a message to the control group. Check the JSFiddle if you want to see it in action.

But wait! I don’t want to see the input red just when I haven’t started filling something out! Of course not, so lets check the validity first after the user has left the input. This is how I do it:

            var hasBeenVisited = false;
// check if user has left the field
element.on("blur", function () {
$scope.$apply(function () {
hasBeenVisited = true;
});
});
// Watch the validity of the input
$scope.$watch(function () {
return modelController.$invalid && hasBeenVisited;
}, function () {
// $emit messages to the control group
if (modelController.$invalid && hasBeenVisited)
$scope.$emit("inputError");
else $scope.$emit("inputValid");
});

I add a hasBeenVisited variable and change it on “blur” on the element. Notice that I need to wrap it in an $scope.$apply block because I also $watch it. I need it to be detected during digest. I extended the condition on which I notify the form group that there is an error. Check the updated JSFiddle to see it live.

But what if i don’t want the formGroup directive to have its own scope, because I find it unnecessary and can’t bind primitive types in the form group? I use a directive controller, which is allows the input directive to notify the parent directive about it’s state. This is the code of formGroup directive.

        //get the controller in the link function
require: "formGroup",
link: function ($scope, element, attributes, controller) {
var errorList = {};
controller.inputError = function () {
element.addClass("has-error");
};
controller.inputValid = function () {
element.removeClass("has-error");
};
},
//the controller is initialized in link function
controller: function() {return {};}

I removed the scope of the formGroup and instead I specified a function to return a controller object. In the link function I assign two functions to it, both used for notification about input state. To be able to access the controller in the link function, I have to specify the directive name as the require property.

Then in the input directive I have to require the formGroup controller and instead of emitting events on $scope I call the controller methods:

        //require controlllers of ngModel and parent directive
//formGroup
//they're injected as array parameter of link function
require: ["?ngModel", "^?formGroup"],
link: function ($scope, element, attributes, controllers) {
var modelController = controllers[0];
var formGroupController = controllers[1];
if (!modelController || !formGroupController) return;
var hasBeenVisited = false;
// check if user has left the field
element.on("blur", function () {
$scope.$apply(function () {
hasBeenVisited = true;
});
});
// Watch the validity of the input
$scope.$watch(function () {
return modelController.$invalid && hasBeenVisited;
}, function () {
// notify the control group
if (modelController.$invalid && hasBeenVisited) {
formGroupController.inputError();
} else {
formGroupController.inputValid();
}
});
}

This is how I add the has-error class to the form group (bootstrap 3) or control group (bootstrap 2). You can try it out in a JSFiddle.

Feel free to use the code in your project. Do you have an different approach? Did I miss something? Is anything unclear? Leave a comment! Did you like the post? +1 or share it! Thank you for your contributions.

Thursday 16 January 2014

AngularJS Loading Widget

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!