karlgold
karlgold

Reputation: 7988

AngularJS: is there a difference between $transclude local in directive controller and transclude parameter in link function?

I implemented a directive that transcludes multiple fragments of child content into a template. It works but seems simpler than most of the examples I have seen, and raised a few questions about how transclusion works.

Here is the directive:

module.directive('myTransclude', function() {
  return {
    restrict: 'A',
    transclude: true,
    replace: true,
    scope: true,
    template: '<div style="border: 1px solid {{color}}"><div class="my-transclude-title"></div><div class="my-transclude-body"></div></div>',
    link: function(scope, element, attrs, controller, transclude) {
      // just to check transcluded scope
      scope.color = "red";
      transclude(scope, function(clone, scope) {
        Array.prototype.forEach.call(clone, function(node) {
          if (! node.tagName) {
            return;
          }
          // look for a placeholder node in the template that matches the tag in the multi-transcluded content
          var placeholder = element[0].querySelector('.' + node.tagName.toLowerCase());
          if (! placeholder) {
            return;
          }
          // insert the transcluded content
          placeholder.appendChild(node);
        });
      });
    }
  }
});

and here is example usage:

<div ng-controller="AppController">
    <div my-transclude>
        <my-transclude-title> <strong ng-bind="title"></strong>

        </my-transclude-title>
        <my-transclude-body>My name is {{name}} and I've been transcluded!</my-transclude-body>
    </div>
</div>

You can see it in action in this fiddle.

Please notice a few things:

  1. It matches fragments to template placeholders by element class, rather than explicitly defined child directives. Is there any reason to do this one way or another?
  2. Unlike many examples I've seen, it doesn't explicitly use the $compile service on the child fragments. It seems like Angular is compiling the fragments after transclusion, at least in this simple case. Is this always correct?
  3. It uses the (barely documented) transclude argument to the link function, rather than the other (barely documented) approach of injecting the $transclude local into the controller directive. After hearing so many admonitions not to manipulate DOM in controllers, the controller approach seems like an odd construct and it feels more natural to handle this in the link function. However, I tried it that way and it seems to work the same. Is there any difference between the two?

Thanks.

EDIT: to partially answer question #2, I discovered that you do need to explicitly compile transcluded content that is not cloned from the template where the directive was applied. See the difference in behavior here: http://jsfiddle.net/4tSqr/3/

Upvotes: 8

Views: 2765

Answers (2)

JayKan
JayKan

Reputation: 4865

To answer your question in regards to the differences between $transclude function in directive controller vs linking function, first we need understand that $transclude function can be accessed through directive compile, controller and linking functions.

UPDATE: According to 1.4 Angular documentation, compile(transclude) has been deprecated! So transclude function can only be accessible in your directive Controller or Linking function. (See official documentation for detail explanation)

There is a big difference when used $transclude in compile phase vs. $transclude in controller and linking phase due to during compiling phase, you don't have access to $scope vs. using in controller and linking functions where $scope (controller) and scope (linking) are accessible. With that said, the only difference in using $transclude in directive controller vs. linking is order of execution. For multiple nested directives, its relatively safe to use $transclude during your linking phase instead of using it in your controller.

The order goes like this:

  1. parentDirectiveCompile -> childDirectiveCompile (Directive Compile)

  2. parentDirectiveControllerPre, parentDirectiveControllerPost -> childDirectiveControllerPre, childDirectiveControllerPost (Directive Controller)

  3. childLinkFunction -> parentLinkFunction

Notice how childLinkFunction gets executed first before parentLinkFunction? (Order of execution)

Helpful Resource:

Hopefully this answer can be helpful to you!

Upvotes: 2

btm1
btm1

Reputation: 3856

after some investigation:

After the release of Angular 1.20 pre-existing child nodes of a compiled directive that has a isolate scope will no longer inherit the new isolate scope because they have already been assigned to the parent scope. So... the built in transclude method that uses the attribute ng-transclude will only transclude templates to the desired location in this case but will not transclude pre-existing html to this location. This means that if you had a directive with an isolate scope and you wanted the html that was already there to be compiled to the new isolate scope you would need to use the transclude linker function inside of the directive.

You can see a working case of this problem here ui-codemirror placed within custom directives fails without an error

Upvotes: 0

Related Questions