Rosher Consulting

Software Consulting and Development Services

Angular Tips: Adding functionality to existing directives

One of the features I really like in Angular, which is not that well known is the ability to create new directives with the same name as existing ones and thereby add some new functionality to them.

When you register two directives with the same name, Angular doesn’t complain, it merely runs those directives in the order they were registered. A common scenario I use this for is to create my own ‘ng-click’ directive to provide some feedback to the user when they click a button, such as disabling it while waiting for an Ajax request to complete.

With the above scenario, if you were to look on Stack Overflow for questions about this, the most common answer would be to create your own directive, say ‘my-own-click’ and use that instead of the standard ‘ng-click’. The downside with this is that you have to re-implement the standard ‘ng-click’ functionality and you have to remember to use your ‘my-own-click’ directive everywhere. If you’re working in a team, this then means all of your co-workers also need to know to use this new directive.

A Better Approach

The following is the basic template I use in my projects to add some feedback to my buttons when an action occurs. The directive requires that you also attach an ‘ng-model’ to your button so that it can use the model variable to know when the action starts and ends:

   1:  angular.module('MyApp').directive('ngClick', function () {
   2:      return {
   3:          restrict: 'A',
   4:          link: function (scope, element, attrs) {
   5:              if (attrs.ngModel) {
   6:                  var el = element.find("span");
   7:                  var cls = el.attr("class");
   9:                  scope.$watch(attrs.ngModel, function (newValue, oldValue) {
  10:                      if (newValue) {
  11:                          if (el.length) {
  12:                              el.attr("class", "glyphicon glyphicon-refresh fa-spin");
  13:                          }
  14:                          element.attr("disabled", true);
  15:                      } else {
  16:                          if (el.length) {
  17:                              el.attr("class", cls);
  18:                          }
  19:                          element.attr("disabled", false);
  20:                      }
  21:                  });
  22:              }
  23:          }
  24:      };
  25:  });

The directive itself does a bit more than just disable the button, it also looks to see if the button contains a child ‘span’ element and if it does, it assumes this is a Glyph Icon as used by Bootstrap and then changes the class of the ‘span’ to the Refresh icon and also uses the ‘fa-spin’ class from Font Awesome to rotate the icon, thereby actively showing the user that their action has caused something to happen.

Here’s the appropriate HTML to use this directive:

   1:  <button class="btn btn-primary" ng-click="save()" ng-model="saving">
   2:      <span class="glyphicon glyphicon-floppy-save"></span>
   3:      Save
   4:  </button>

And the ‘save’ function in your controller (this is calling a save method on a service which returns a promise, so we’re handling both the success and failure of the promise and updating the model variable appropriately):

   1:  $ = function () {
   2:      $scope.saving = true;
   3:$ {
   4:          $scope.saving = false;
   5:      }, function() {
   6:          $scope.saving = false;
   7:      });
   8:  };

As you can see, as long as you update the appropriate model variable in your controller, your button will automatically be updated without you having to remember to use the appropriate directive.

Obviously this isn’t totally free, in that you still need to do some work, but this could certainly be improved further by creating your own http interceptor for instance in combination with the custom ‘ng-click’ directive to identify which button was clicked and update appropriately.

Overall I love how easy Angular makes it to do things like this and your users will appreciate the feedback from your UI.

Comments are closed