In the transclude function of a directive link function, how is "futureParentElement" used?

2.3k views Asked by At

In the angular documentation for the compile service (starting at line 412) there is a description of the transclude function that is passed into the linking function of a directive.

The relevant part reads:

function([scope], cloneLinkingFn, futureParentElement)

In which (line 212):

futureParentElement: defines the parent to which the cloneLinkingFn will add the cloned elements.

  • default: $element.parent() resp. $element for transclude:'element' resp. transclude:true.

  • only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements) and when the cloneLinkinFn is passed, as those elements need to created and cloned in a special way when they are defined outside their usual containers (e.g. like <svg>).

  • See also the directive.templateNamespace property.

I fail to see the point of futureParentElement however. It says

defines the parent to which the cloneLinkingFn will add the cloned elements.

But you do that in the cloneLinkingFn itself like this:

transclude(scope, function (clone) {
    some_element.append(clone);
});

And you can't use the transclude function without defining the cloning function in the first place.

What is the proper usage/a use for futureParentElement?

1

There are 1 answers

2
Michal Charemza On BEST ANSWER

The answer to this can be found by looking at the the git blame of compile.js: the commit that added futureParentElement is https://github.com/angular/angular.js/commit/ffbd276d6def6ff35bfdb30553346e985f4a0de6

In the commit there is a test that tests a directive svgCustomTranscludeContainer

directive('svgCustomTranscludeContainer', function() {
  return {
    template: '<svg width="400" height="400"></svg>',
    transclude: true,
    link: function(scope, element, attr, ctrls, $transclude) {
      var futureParent = element.children().eq(0);
      $transclude(function(clone) {
        futureParent.append(clone);
      }, futureParent);
    }
  };
});

by testing how compiling the html <svg-custom-transclude-container><circle cx="2" cy="2" r="1"></circle> behaves:

it('should handle directives with templates that manually add the transclude further down', inject(function() {
  element = jqLite('<div><svg-custom-transclude-container>' +
      '<circle cx="2" cy="2" r="1"></circle></svg-custom-transclude-container>' +
      '</div>');
  $compile(element.contents())($rootScope);
  document.body.appendChild(element[0]);

  var circle = element.find('circle');
  assertIsValidSvgCircle(circle[0]);
}));

So it looks like if you are creating an SVG image with a directive whose template wraps transcluded SVG content in <svg> ... </svg> tags, then that SVG image won't be valid (by some definition), if you don't pass the correct futureParentElement to $transclude.

Trying to see what it actually means not to be valid, beyond the test in the source code, I created 2 directives based on the ones in the unit test, and used them to try to create an SVG image with partial circle. One using the futureParentElement:

<div><svg-custom-transclude-container-with-future><circle cx="1" cy="2" r="20"></circle></svg-custom-transclude-container></div>

and one that is identical but that doesn't:

<div><svg-custom-transclude-container-without-future><circle cx="2" cy="2" r="20"></circle></svg-custom-transclude-container></div>

As can be seen at http://plnkr.co/edit/meRZylSgNWXhBVqP1Pa7?p=preview, the one with the futureParentElement shows the partial circle, and the one without doesn't. The structure of the DOM appears identical. However Chrome seems to report that the second circle element isn't an SVG node, but a plain HTML node.

So whatever futureParentElement actually does under the hood, it seems to make sure that transcluded SVG content ends up being handled as SVG by the browser.