meum meum - 6 months ago 37
AngularJS Question

AngularJS: Transcluded content in directive disappears

I have a list in my controller which is used by ng-repeat to create my directive. In my directive I use transclusion to append to the directive's element.

When adding the first item to my list it seems to work as expected, but when adding a second item the transcluded content disappears from the element of the first item. This makes no sense to me so there must be something I'm misunderstanding.

I have created an example which recreates the problem.

After the first item is added, the html looks like expected:

<div ng-controller="ctrl as c" class="ng-scope">
<test ng-repeat="item in c.items track by $index" item="item" class="ng-scope ng-isolate-scope">
<div class="ng-binding">1</div>
<span class="ng-scope">Transcluded</span>
</test>
</div>


After the second item is added, the Transcluded content is no longer in the element of the first item!

<div ng-controller="ctrl as c" class="ng-scope">
<test ng-repeat="item in c.items track by $index" item="item" class="ng-scope ng-isolate-scope">
<div class="ng-binding">1</div>
</test>
<test ng-repeat="item in c.items track by $index" item="item" class="ng-scope ng-isolate-scope">
<div class="ng-binding">2</div>
<span class="ng-scope">Transcluded</span>
</test>
</div>


HTML:

<div ng-app="ui">
<div ng-controller="ctrl as c">
<test ng-repeat="item in c.items track by $index" item="item">Transcluded</test>
</div>
</div>


Typescript:

var app = angular.module('ui', []);

class Controller {
public items = [];

constructor($timeout) {
$timeout(() => this.items.push({Id: 1}), 1000);
$timeout(() => this.items.push({Id: 2}), 2000);
}
}
app.controller('ctrl', ['$timeout', Controller]

app.directive('test', function($compile) {
return {
scope: {
item: '='
},
transclude: true,
template: "<div>{{item.Id}}</div>"
link: function(scope, element, attrs, controller, transcludeFn) {
console.log("Appending transcluded content to " + scope.item.Id)
let e = transcludeFn();
element.append(e);
}
};
});


JSFiddle: https://jsfiddle.net/rmytw9cr/2/

Answer

Unless you need to use the transclude function, don't use it (checkout transclution on google for more info, it is a bit complex, you can for example compile the transcluded part with scope of your choice).

You can use the transclude directive for a simple transclution.

Try the following template, and remove the transclude from your directive:

template: "<div><div>{{item.Id}}</div><div ng-transclude></div></div>"

Update: To use of the transclusion function, you need to understand how it works. The transclusion function gets a callback as a first argument. The callback function will then get as a first argument the transcluded html element.

So the proper way to use it, would be:

transcludeFn(function(compiledHtml) {
    // Do what ever you want with the complied HTML
    // For example: element.append(compiledHtml);
});

The transclusion function can also get another argument, which is the scope to compile the HTML with. In that case, you should supply the scope as the first argument, and the callback with the compiled html as the second:

transcludeFn(someScope, function(compiledHtml) {
    // Do what ever you want with the complied HTML
    // For example: element.append(compiledHtml);
});

And for the final - the callback can also get a second argument, which is the transcluded scope:

transcludeFn(someScope, function(compiledHtml, transScope) {
    // Do what ever you want with the complied HTML
    // For example: element.append(compiledHtml);
    // Here transScope will be the same as someScope
});

transcludeFn(function(compiledHtml, transScope) {
    // Do what ever you want with the complied HTML
    // For example: element.append(compiledHtml);
    // Here transScope will be the same as directive scope
});
Comments