How can I dynamically add a directive in AngularJS?

AngularjsAngularjs DirectiveDynamically Generated

Angularjs Problem Overview


I have a very boiled down version of what I am doing that gets the problem across.

I have a simple directive. Whenever you click an element, it adds another one. However, it needs to be compiled first in order to render it correctly.

My research led me to $compile. But all the examples use a complicated structure that I don't really know how to apply here.

Fiddles are here: http://jsfiddle.net/paulocoelho/fBjbP/1/

And the JS is here:

var module = angular.module('testApp', [])
    .directive('test', function () {
    return {
        restrict: 'E',
        template: '<p>{{text}}</p>',
        scope: {
            text: '@text'
        },
        link:function(scope,element){
            $( element ).click(function(){
                // TODO: This does not do what it's supposed to :(
                $(this).parent().append("<test text='n'></test>");
            });
        }
    };
});

Solution by Josh David Miller: http://jsfiddle.net/paulocoelho/fBjbP/2/

Angularjs Solutions


Solution 1 - Angularjs

You have a lot of pointless jQuery in there, but the $compile service is actually super simple in this case:

.directive( 'test', function ( $compile ) {
  return {
    restrict: 'E',
    scope: { text: '@' },
    template: '<p ng-click="add()">{{text}}</p>',
    controller: function ( $scope, $element ) {
      $scope.add = function () {
        var el = $compile( "<test text='n'></test>" )( $scope );
        $element.parent().append( el );
      };
    }
  };
});

You'll notice I refactored your directive too in order to follow some best practices. Let me know if you have questions about any of those.

Solution 2 - Angularjs

In addition to perfect Riceball LEE's example of adding a new element-directive

newElement = $compile("<div my-directive='n'></div>")($scope)
$element.parent().append(newElement)

Adding a new attribute-directive to existed element could be done using this way:

Let's say you wish to add on-the-fly my-directive to the span element.

template: '<div>Hello <span>World</span></div>'

link: ($scope, $element, $attrs) ->
  
  span = $element.find('span').clone()
  span.attr('my-directive', 'my-directive')
  span = $compile(span)($scope)
  $element.find('span').replaceWith span

Hope that helps.

Solution 3 - Angularjs

Dynamically adding directives on angularjs has two styles:

Add an angularjs directive into another directive

  • inserting a new element(directive)
  • inserting a new attribute(directive) to element
inserting a new element(directive)

it's simple. And u can use in "link" or "compile".

var newElement = $compile( "<div my-diretive='n'></div>" )( $scope );
$element.parent().append( newElement );
inserting a new attribute to element

It's hard, and make me headache within two days.

Using "$compile" will raise critical recursive error!! Maybe it should ignore the current directive when re-compiling element.

$element.$set("myDirective", "expression");
var newElement = $compile( $element )( $scope ); // critical recursive error.
var newElement = angular.copy(element);          // the same error too.
$element.replaceWith( newElement );

So, I have to find a way to call the directive "link" function. It's very hard to get the useful methods which are hidden deeply inside closures.

compile: (tElement, tAttrs, transclude) ->
   links = []
   myDirectiveLink = $injector.get('myDirective'+'Directive')[0] #this is the way
   links.push myDirectiveLink
   myAnotherDirectiveLink = ($scope, $element, attrs) ->
       #....
   links.push myAnotherDirectiveLink
   return (scope, elm, attrs, ctrl) ->
       for link in links
           link(scope, elm, attrs, ctrl)       

Now, It's work well.

Solution 4 - Angularjs

function addAttr(scope, el, attrName, attrValue) {
  el.replaceWith($compile(el.clone().attr(attrName, attrValue))(scope));
}

Solution 5 - Angularjs

Josh David Miller is correct.

PCoelho, In case you're wondering what $compile does behind the scenes and how HTML output is generated from the directive, please take a look below

The $compile service compiles the fragment of HTML("< test text='n' >< / test >") that includes the directive("test" as an element) and produces a function. This function can then be executed with a scope to get the "HTML output from a directive".

var compileFunction = $compile("< test text='n' > < / test >");
var HtmlOutputFromDirective = compileFunction($scope);

More details with full code samples here: http://www.learn-angularjs-apps-projects.com/AngularJs/dynamically-add-directives-in-angularjs

Solution 6 - Angularjs

The accepted answer by Josh David Miller works great if you are trying to dynamically add a directive that uses an inline template. However if your directive takes advantage of templateUrl his answer will not work. Here is what worked for me:

.directive('helperModal', [, "$compile", "$timeout", function ($compile, $timeout) {
    return {
        restrict: 'E',
        replace: true,
        scope: {}, 
        templateUrl: "app/views/modal.html",
        link: function (scope, element, attrs) {
            scope.modalTitle = attrs.modaltitle;
            scope.modalContentDirective = attrs.modalcontentdirective;
        },
        controller: function ($scope, $element, $attrs) {
            if ($attrs.modalcontentdirective != undefined && $attrs.modalcontentdirective != '') {
                var el = $compile($attrs.modalcontentdirective)($scope);
                $timeout(function () {
                    $scope.$digest();
                    $element.find('.modal-body').append(el);
                }, 0);
            }
        }
    }
}]);

Solution 7 - Angularjs

Inspired from many of the previous answers I have came up with the following "stroman" directive that will replace itself with any other directives.

app.directive('stroman', function($compile) {
  return {
	link: function(scope, el, attrName) {
	  var newElem = angular.element('<div></div>');
      // Copying all of the attributes
	  for (let prop in attrName.$attr) {
	    newElem.attr(prop, attrName[prop]);
	  }
      el.replaceWith($compile(newElem)(scope)); // Replacing
	}
  };
});

Important: Register the directives that you want to use with restrict: 'C'. Like this:

app.directive('my-directive', function() {
  return {
    restrict: 'C',
    template: 'Hi there',
  };
});

You can use like this:

<stroman class="my-directive other-class" randomProperty="8"></stroman>

To get this:

<div class="my-directive other-class" randomProperty="8">Hi there</div>

Protip. If you don't want to use directives based on classes then you can change '<div></div>' to something what you like. E.g. have a fixed attribute that contains the name of the desired directive instead of class.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPCoelhoView Question on Stackoverflow
Solution 1 - AngularjsJosh David MillerView Answer on Stackoverflow
Solution 2 - AngularjsdeadrunkView Answer on Stackoverflow
Solution 3 - AngularjsRiceball LEEView Answer on Stackoverflow
Solution 4 - Angularjsuser1212212View Answer on Stackoverflow
Solution 5 - AngularjsDanial LokmanView Answer on Stackoverflow
Solution 6 - Angularjsferics2View Answer on Stackoverflow
Solution 7 - AngularjsGábor ImreView Answer on Stackoverflow