41

I was reading about ng-transclude in the AngularJS docs on Creating a Directive that Wraps Other Elements and I think I understand properly what it does.

If you have a directive that applies to an element that has content inside it such as the following:

<my-directive>directive content</my-directive>

it will allow you to tag an element within the directive's template with ng-transclude and the content included in the element would be rendered inside the tagged element.

So if the template for myDirective is

<div>before</div>
<div ng-transclude></div>
<div>after</div>

it would render as

<div>before</div>
<div ng-transclude>directive content</div>
<div>after</div>

My question is if it is possible to somehow pass more then a single block of html into my directive?

For example, suppose the directive usage would look like this:

<my-multipart-directive>
     <part1>content1</part1>
     <part2>content2</part2>
</my-multipart-directive>

and have a template like:

<div>
  this: <div ng-transclude="part2"></div>
   was after that: <div ng-transclude="part1"></div>
   but now they are switched
<div>

I want it to render as follows:

<div>
  this: <div ng-transclude="part2">content2</div>
   was after that: <div ng-transclude="part1">content1</div>
   but now they are switched
<div>

Perhaps I could somehow bind the HTML value of a node to the model so that I will be able to use it in such a way without calling it "transclude"?

1
  • although I have accepted hassassin's answer quite a while ago. It would seem that the correct and up-to-date way to do this is about to change soon. see kevinius answer. Commented Dec 3, 2015 at 11:34

3 Answers 3

38

Starting Angular 1.5, it's now possible to create multiple slots. Instead of transclude:true, you provide an object with the mappings of each slot:

https://docs.angularjs.org/api/ng/directive/ngTransclude

angular.module('multiSlotTranscludeExample', [])
 .directive('pane', function(){
    return {
      restrict: 'E',
      transclude: {
        'title': '?pane-title',
        'body': 'pane-body',
        'footer': '?pane-footer'
      },
      template: '<div style="border: 1px solid black;">' +
                  '<div class="title" ng-transclude="title">Fallback Title</div>' +
                  '<div ng-transclude="body"></div>' +
                  '<div class="footer" ng-transclude="footer">Fallback Footer</div>' +
                '</div>'
    };
})
Sign up to request clarification or add additional context in comments.

7 Comments

nice, They use my above suggested syntax almost as is. too bad that they did not update the docs for the transclude docs.angularjs.org/api/ng/service/$compile. I am also not sure I understand the naming convention of the the transclude object properties and their relation to the HTML elements of the directive's template and those in the HTML that uses the directive.
it looks like the object is a mapping between the normalized transcluded elements tag names (i.e <pane-title> normalized to paneTitle) and the value of ng-transclude attribute of the directives template element .
IMHO this is counter intuitive and I would have expected it to be the other way around in a similar fashion to how it works with scope:{} i.e. that the resolved value for the template would be on the left and value would give the instructions on where to get it from.
please note that following the above issue the keys/values in the transclude object have been swapped. so it should now be transclude: { 'title':'?pane-title', 'body':'pane-body' }, I still asked for another change which is to use 'paneTitle' and 'paneBody' which are again more consistent with the scope object syntax. I hope they will accept this request as well.
in Angular 1.6.4 they are using the syntax: paneTitle instead of pane-title.
|
23

Cool question. I'm not sure there is a built in way, but you can do it yourself in a pretty generic way.

You can access the transcluded element by passing in the $transclude service like this:

$transclude(function(clone, $scope) {

Where clone will be a copy of the pre-linked transcluded content. Then if you label the content in the element like this:

    <div id="content">
        <div id="content0">{{text}}</div>
        <div id="content1">{{title}}</div>
    </div>

You can loop over the content and compile it like this:

$scope.transcludes.push($compile(clone[1].children[i])($scope));

Great! now you just need to put the content in the correct place in your template

     '<div id="transclude0"></div>' +
     '<div id="transclude1"></div>' +

Then you can in your link function assign the content correctly

angular.element(document.querySelector('#transclude' + i)).append(scope.transcludes[i]);

I set up a fiddle you can play with that has this set up.

Hope this helped!

8 Comments

thanks. this looks very close to what I was thinking of. I am not sure I understand what the $transclude service actually does but I guess I can read about it in the docs. why did you choose to have "div#content" as a wrapper of "div#content0" and "div#content1" and what would change if content was not there to wrap them? also what exactly is in the clone parameter? (what is clone[0] if clone[1] is div#content?)
well... I went to the docs and did not find any reference to $transclude ... where did you find it and how to use it?
You can see it in the transclude directive github.com/angular/angular.js/blob/… It is defined here: github.com/angular/angular.js/blob/… and described here: github.com/angular/angular.js/blob/…
I ended up needing similar functionality and wrote ng-multi-transclude. It allows you to do almost exactly what you requested: in the template you define named "holes" via ng-multi-transclude, and at include time you pass in multiple elements whose name attributes match the holes you'd like them to fill.
For anyone curious where this functionality is documented, search for $transclude in the $compile docs
|
2

In our project we have modeled multi site trasclusion after JSF 2's ui:composition, ui:insert, ui:define (see ui:composition).

Implementation consists of three simple directives: ui-template, ui-insert, ui-define (see angularjs-api/template/ui-lib.js).

To define a template one writes the following markup (see angularjs-api/template/my-page.html):

<table ui-template>
  <tr>
    <td ui-insert="menu"></td>
  </tr>
  <tr>
    <td ui-insert="content"></td>
  </tr>
</table>

and declares a directive (see angularjs-api/template/my-page.js):

  var myPage = 
  {
    templateUrl: "my-page.html",
    transclude: true
  };

  angular.module("app").
    directive("myPage", function() { return myPage; });

and finally, to instantiate the directive one needs to write (see angularjs-api/template/sample.html):

<my-page>
  <div ui-define="content">
    My content
  </div>
  <div ui-define="menu">
    <a href="#file">File</a>
    <a href="#edit">Edit</a>
    <a href="#view">View</a>
  </div>
</my-page>

The working sample can be seen through rawgit: sample.html

See also: Multisite Transclusion in AngularJS

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.