Dynamically add directives in AngularJS (while ditching jQuery)

Posted on January 21 2014

Let’s say you want to add a button that adds more buttons (or any other interactive element) that do something when you click them.

There is a few ways to achieve this, but most of the solutions I found either didn’t work (probably due to using an older AngularJS version), seemed unnecessarily complicated or relied on using jQuery.

Here is what I came up with (for Angular 1.2.9).

To see a working example with the full code, visit this page. I’m going to walk you through it here.

The HTML markup is very simple.

<section ng-app="myApp" ng-controller="MainCtrl">
  <addbuttonsbutton></addbuttonsbutton>
  <div id="space-for-buttons"></div>
</section>

You can of course define your addbuttonsbutton element directly in the HTML file, the way I’m doing it here is only to illustrate different methods of adding directives dynamically.

If you want to do it directly, your HTML will look like this:

<section ng-app="myApp" ng-controller="MainCtrl">
  <button addbuttons>Click to add buttons</button>
  <div id="space-for-buttons"></div>
</section>

In your JS file, you will first want to start with some boilerplate code:

var myApp = angular.module(‘myApp’, []);

function MainCtrl($scope) {
  $scope.count = 0;
}

The count variable is going to be useful in this example to make things more visual.

Now comes the main part.

This is the directive that returns our "Add more buttons" element. When you click it, a new button will be added to the "space-for-buttons" div.

myApp.directive("addbuttonsbutton", function(){
  return {
    restrict: "E",
    template : "<button addbuttons>Click to add buttons</button>"
  }
});

This is the directive that adds our new buttons:

myApp.directive("addbuttons", function($compile){
  return function(scope, element, attrs){
    element.bind("click", function(){
      scope.count++;
      angular.element(document.getElementById('space-for-buttons'))
             .append($compile("<div><button class='btn btn-default' data-alert="+scope.count+">Show alert #"+scope.count+"</button></div>")
             (scope));
    });
  };
});

Let’s pause for a moment here. To explain the use of 

If jQuery is available, angular.element is an alias for the jQuery function. If jQuery is not available, angular.element delegates to Angular’s built-in subset of jQuery, called "jQuery lite" or "jqLite."

Have a look at the documentation page to see the list of supported jQuery methods.

To use jqLite, you will have to wrap the native JavaScript methods like document.getElementById or document.querySelectorAll with the angular.element function. This may take more space, but you can easily create your own jQuery-style selector function, something like this:

var $ = function(selector){
  return angular.element(document.querySelectorAll(selector));\
}

Voila, you are now running jQuery-free and can still use the most common jQuery functions (including append, addClass, css, parent and quite a few more) just the way you are used to.

As a side note, document.querySelectorAll has the same exact browser support as Angular - all major browsers and IE 8+.

Moving on.

If you simply insert this code into your HTML

<div>
  <button data-alert="+scope.count+">
    Show alert #“+scope.count+”
  </button>
</div>

it won’t do anything. This is where the $compile service comes in. It simply ensures that your HTML code gets compiled as Angular code.

Finally, the directive for showing an alert.

myApp.directive("alert", function(){
  return function(scope, element, attrs){
    element.bind("click", function(){
     console.log(attrs);
     alert("This is alert #" + attrs.alert);
    });
  };
});

And that’s it, folks!