Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to replace an element in AngularJS directive linking function?

I'm creating a <row> AngularJS directive that needs to replace itself (the <row> tag must not be present in the DOM after execution) with a dynamic template that can contain any HTML code.

The problem in using replace: true is that it does not work with table's <tr> tags and that the template is dynamically chosen.

So I'm trying to find a way to replace the element in the linking function, with no success.

Using jQuery's .replaceWith() breaks ngRepeat for unknown reason.

Any hints?

Here is the fiddle

like image 578
Antonio Madonna Avatar asked Jun 13 '13 15:06

Antonio Madonna


People also ask

What is Link function in AngularJS directive?

Link: The link function deals with linking scope to the DOM. Using Code for Compile. While defining a custom directive we have the option to define a link against which either we can define a function or we have the option to assign an object which will have pre & post function.

Which directive definition option is used to replace the current element if true?

As the documentation states, 'replace' determines whether the current element is replaced by the directive. The other option is whether it is just added to as a child basically.

What is the difference between controller and link in directives in AngularJS?

The link option is just a shortcut to setting up a post-link function. controller: The directive controller can be passed to another directive linking/compiling phase. It can be injected into other directices as a mean to use in inter-directive communication.


2 Answers

Mark's answer will work, however, that example is too limited to show the whole picture. Whereas Mark's directive might indeed suffice for common and simple UI components, for more complex operations, that pattern is one to be avoided. Below I explain in detail the reason behind this. In fact, Angular already provides a far simpler way to replace the directive element with a template. It can be found at the bottom of this answer.

Here is how a directive looks behind the scenes:

.directive('row', function ($compile) {
  return {
      restrict: 'E',
      scope: {
          items: "="
      },

      // Whether you define it this way or not, this is the order of
      // operation (execution) behind every AngularJS directive.
      // When you use the more simple syntax, Angular actually generates this
      // structure for you (this is done by the $compile service):

      compile: function CompilingFunction($templateElement, $templateAttributes, transcludeFn) {

        // The compile function hooks you up into the DOM before any scope is
        // applied onto the template. It allows you to read attributes from
        // the directive expression (i.e. tag name, attribute, class name or
        // comment) and manipulate the DOM (and only the DOM) as you wish.

        // When you let Angular generate this portion for you, it basically
        // appends your template into the DOM, and then some ("some" includes
        // the transclude operation, but that's out of the $scope of my answer ;) )

          return function LinkingFunction($scope, $element, $attrs) {

            // The link function is usually what we become familiar with when
            // starting to learn how to use directives. It gets fired after
            // the template has been compiled, providing you a space to
            // manipulate the directive's scope as well as DOM elements.

            var html ='<div ng-repeat="item in items">I should not be red</div>';
            var e = $compile(html)($scope);
            $element.replaceWith(e);
          };
      }
  };
});

What can we make out of that? It is obvious then, that manually calling $compile for the same DOM layout twice is redundant, bad for performance and bad for your teeth, too. What should you do instead? Simply compile your DOM where it should be compiled:

.directive('row', function ($compile) {
  return {
      restrict: 'E',
      template: '<div ng-repeat="item in items">I should not be red</div>',
      scope: {
          items: "="
      },

      compile: function CompilingFunction($templateElement, $templateAttributes) {
          $templateElement.replaceWith(this.template);

          return function LinkingFunction($scope, $element, $attrs) {
            // play with the $scope here, if you need too.
          };
      }
  };
});

If you want to dive in further under the hood of directives, here is what I like to call the Unofficial AngularJS Directive Reference

Once you're done with that head over here: https://github.com/angular/angular.js/wiki/Understanding-Directives


Now, as promised, here is the solution you came here for:

Using replace: true:

.directive('row', function ($compile) {
    return {
        restrict: 'E',
        template: '<div ng-repeat="item in items">I should not be red</div>',
        replace: true,
        scope: {
            items: "="
        }
    };
});
like image 82
pilau Avatar answered Oct 17 '22 01:10

pilau


Your fiddle seems pretty basic but you should be able to just use outerHTML

element[0].outerHTML ='<div>I should not be red</div>';

Updated fiddle

If you have to deal with ng-repeat you can bind your items to a scope property and reference them in your template that you compile. Once it is compiled you can use jQuery replaceWith()

html

<row items="items">***</row>

directive

.directive('row', function ($compile) {
    return {
        restrict: 'E',
        scope: {
            items: "="
        },
        link: function (scope, element, attrs) {
            var html ='<div ng-repeat="item in items">I should not be red</div>';
            var e =$compile(html)(scope);
            element.replaceWith(e);
        }
    };
});

ng-repeat example

like image 54
Mark Coleman Avatar answered Oct 17 '22 03:10

Mark Coleman