AngularJS: Simple, reusable directives

Directives in Angular give you the power to do a lot. Sometimes, you need to do a lot. Most of the time you just need something simple that will get the job done. Here are a few examples of reusable directives that do only what they need to do.

First, a directive that can be applied to a button group. Keep in mind this isn’t restricted to a button group, and if you think creatively, it can come be applied in a lot of places.

  mod.directive('radioValue', function () {
    return {
      restrict: 'A',
      require: '^ngModel',
      scope: {
        radioValue: '='
      },
      link: function (scope, elem, attrs, ngModelCtrl) {
        elem.on('click', function () {
          scope.$apply(function () {
            ngModelCtrl.$setViewValue(scope.radioValue);
          });
        });
        scope.$watch(function() {
          return ngModelCtrl.$modelValue;
        }, function(newVal) {
          elem.toggleClass('selected', newVal === scope.radioValue);
        });
      }
    };
  });

Not impressed? You don’t have to be. Keep it simple. Here’s an example of how this directive would be used.

<div class='btn-group' ng-model='ctrl.animal'>
    <div class='btn' radio-value='"Dog"'>Dog</div>
    <div class='btn' radio-value='"Cat"'>Cat</div>
    <div class='btn' radio-value='"Pig"'>Pig</div>
</div>

Full demo: http://jsfiddle.net/xQCRB/2/

The beauty of the directive, to me, is that we take advantage of an existing controller and the directive’s ability to require parent controllers to provide mutually exclusive selection on a model value.

Here’s one more simple directive and how it can be used. It’s actually quite similar.

app.directive('setValueOnClick', function () {
  return {
    restrict: 'A',
    require: '^ngModel',
    scope: {
      setValueOnClick: '='
    },
    link: function (scope, elem, attrs, ngModelCtrl) {
      elem.on('click', function () {
        scope.$apply(function () {
          ngModelCtrl.$setViewValue(scope.setValueOnClick);
        });
      });
    }
  };
});

And it’s usage

<div ng-controller="Controller as ctrl">
    <div ng-model='ctrl.number'>
    <div class='number' ng-repeat='num in ctrl.numbers' set-value-on-click='num'>{{num}}</div>
    </div>
    <h2>{{ctrl.number}}</h2>
</div>

Working example: http://jsfiddle.net/NsUvV/1/

The latter directive can be used in dropdown menus and several other places. It saves us the trouble of adding a controller callback, and can be applied to any DOM element quite easily. It is especially useful within ng-repeat to easily set the value to a property of a repeated element.

You could also alter it to have an ng-model on each repeated element, removing the need to use ng-model on the parent.

And that’s it, really. I just wanted to show how a lot of times simple solutions are all we need. You can go grab Bootstrap, Foundation, or any custom CSS, and easily build a dropdown menu or button group, then apply a few attributes to have a fully functional control.

The implementations above are not optimal and are intended for easy consumption. By utilizing $parse and making assumptions that the value we are assigning won’t change, we can save a few watch expressions. For the radioValue directive you could refactor away the watching of the model value as well with a little work. These micro-optimizations are nice, but if your web application gets to a point where they are necessary, you probably have too much going on. I like to keep the code clean and readable whenever possible (even at the cost of a bit of performance).

For an optimized and more advanced implementation I recommend taking a look at some of the directives in AngularStrap. Here is the source for their button directives.

https://github.com/mgcrea/angular-strap/blob/master/src/button/button.js

Where bsRadioGroup and bsRadio would be the radio buttons.

 
37
Kudos
 
37
Kudos

Now read this

Simplify async code with generators and promises

Asynchronous code is a necessary part of life when developing in JavaScript. Why not make it easy on yourself? Start with promises. They reduce the need for callback functions and practically eliminate nested callbacks. If you aren’t... Continue →