Stack OverflowLinkedInGitHubEmail

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.