AngularJS : How to properly transclude child elements in custom directive?

1.2k views Asked by At

Here's some code : link

I'm trying to create a directive that wraps its children in some boilerplate. But if the children have ng-if controlling their appearance, the "transclusion" doesn't work. Well it sort of does, but as you can see the ng-if logic is not getting passed through correctly.

I'd like to know how to fix it, but also where (if anywhere) this is described in the Angular docs.

2

There are 2 answers

1
KayakDave On BEST ANSWER

The problem is that Angular initially replaces ngIf with a comment that it uses to track where to place the conditional code. It's easiest to see with an example.

Your HTML:

<div control-group>
  <label>Test</label>
  <input type="text" ng-model="editing.name" />
  <span class="text-error" ng-if="editing.name.length != 3"> Name must be 3 characters </span>
</div>

Looks like this inside your transclude function's cloned variable (transclude(function (cloned) {):

<div control-group>
  <label>Test</label>
  <input type="text" ng-model="editing.name" class="ng-valid ng-dirty">
  <!-- ngIf: editing.name.length != 3 -->
</div>

So, the element with class text-error that you're filtering on (below) isn't in cloned. Just the comment is there.

var inputsAndMessages = cloned.filter('input, button, select, .text-error');

Since you're only transcluding elements that match the above filter the ngIf comment is lost.

The solution is to filter for comments as well and add them in your append (so Angular maintains it's reference point to the ngIf). One way to do that is to replace the above with this (using the fact that an html comment is node type 8)

var messages = cloned.filter(function(){ return this.nodeType == 8; }); //comments

var inputs = cloned.filter('input, button, select')

var inputsAndMessages = inputs.add(messages);

Working plunker

1
John Ledbetter On

You need to tell the directive where to place child elements with the ng-transclude directive: (plnkr)

 template: "<div class='control-group' ng-transclude>" +
                 "<label class='control-label' for=''></label>" +
                 "<div class='controls'></div>" +
              "</div>",

From the docs:

Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion.

Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted.

I wasn't sure what exactly your intent was since you have the input and label both in the template and as children in the HTML. You might want to place your ng-transclude elsewhere.