Transclusion: field-strip

Transclude they said. But what the f**k does it mean? Is this a real word or just fancy neologism?


So let’s start from very begining. Some definitions:

In computer science, transclusion is the inclusion of a document or part of a document into another document by reference.
wikipedia.org

A transclusion is when you import a bunch of stuff here from over there so that it appears as if it were native to here while in reality still being over there. […], a way of making a single object appear to be in many places at once.
c2.com

A transclusion is the reuse in whole or in part of another node in one node’s rendering.
meatballwiki.org

All those definitions say: Transclusion is inserting a reference to (not a copy of) part of a one thing inside another thing. Is that true in context of AngularJS directives transclusion? Let’s try out the most popular transclude pattern…

Introducing ng-transclude & transclude: true

An AngularJS directive configuration object can have the field transclude: true declared, which is meant to transclude the content (i.e. the child nodes) of the directive’s element. And in most examples it’s paired with template: ' ... ' or templateUrl: ' ... '. The ngTransclude directive short definition says that it marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion.

Theory seems to catch up with spirit of transclusion. So it’s time for practical example. Let’s define simple widget directive (it should only wrap contents in ‘section and title‘ html):

app.directive('widget', function () {
    return {
        template: '<section>' +
                      '<h3>Widget</h3>' +
                      '<div ng-transclude></div>' +
                  '</section>',
        transclude: true
    };
});

And put it inside our app:

<div widget><p>Bla bla blabla bla blabla bla…</p></div>

After compilation we get:

<div widget>
    <section>
        <h3>Widget</h3>
        <div ng-transclude>
            <p class="ng-scope">Bla bla blabla bla blabla bla…</p>
        </div>
    </section>
</div>

Check the fiddle: jsfiddle.net/ulfryk/yfrn3th2

So everything works as expected and it’s all well described around the web. But what is transclude: 'element' for and how to handle fifth argument of linking functions – $transcludeFn?

Introducing ‘element’ transclusion

An AngularJS directive configuration object can have the field transclude: 'element' declared, which is meant to transclude the whole element including any directives defined at lower priority. Docs also state that When used, the template property is ignored. So it’s a HTML element and it was removed from the DOM… but when was it removed? …where is the inserting a reference part?

The answers are:

  • the element was removed after all templates and compiles are resolved for the element and link functions of all directives (defined on same element) with HIGHER priority where executed (ones with lower are suspended)
  • to insert the element back (actually it’s a clone not a reference) we need $transcludeFn
  • link functions of all directives (defined on same element) with LOWER priority and those for nested elements will be executed after element is inserted back

So it is almost what can be expected. The only violation of transclusion definition is that element is cloned not referenced, but it surely is a feature not a bug 🙂

This slightly complicated mechanism seems to be great tool to keep clones $compiled and synced with application $scopes. $transcludeFn is used in many built in directives, e.g. ng-if and ng-repeat.

The Transclusion

So the AngularJS definition of transclusion may be:

  1. Grab part of HTML
  2. Hide it (but keep in memory) – exclude it
  3. Keep pointer to it’s initial location
  4. Provide cloning tool
  5. Provide linking and inclusion tool

A visual decomposition (taken from my presentation for meet.js) is just on top of this post.

Further Reading