Reputation: 6172
When composing my view out of various HTML partials, I prefer the declarative approach of placing an ng-include
directive in my markup.
However a third party widget for a slideShow / carousel that I need to populate with items dynamically does not play well with the dynamic DOM changes of ng-repeat
and ng-include
(the third party widget will either accept a set of child nodes to be present in the DOM at the point in time the widget is initialized, or accept that new items are added programmatically by calling an addItem
function).
<!-- this will not work properly, the items collection changes dynamically -->
<div id="slideShowHost">
<div class="slideShowItem"
ng-repeat="item in controller.items"
ng-include="'templates/slideShowItem.html'">
</div>
</div>
Therefore I will replace the slideShowHost content with equivalent code in my controller. I use RequireJS to load the content of templates/slideShowItem.html as one string, and jQuery to create corresponding nodes from it for all my items. But this will not suffice as I use angular directives in the slideShowItem template that need to come to life.
// I'm using typescript here, but answers may as well use untyped javascript
var slideShowHost = $("#slideShowHost").acmeSlideShow();
require(["slideShowItemTemplate-html"], (itemTemplate: string) =>
{
for (var i = 0; i < this.items.length; i++)
{
let initializedItem = this.initAngularDependentItem(this.items[i], itemTemplate);
slideShowHost.addAcmeSlideShowItem( initializedItem );
}
});
...
private initAngularDependentItem(itemModel: any, template: string): any
{
let itemView = $(template);
// we have to create a scope here containing the itemModel
// and we need to init the angular directives inside itemView
return itemView;
}
Which code would be a correct and reliably working replacement for any ng-include
where the included partial may use any angular directives and even nested ng-includes?
Upvotes: 0
Views: 276
Reputation: 6172
The answer given by jcubic is correct. I'd like to add some detail.
We need to have the $compile
service injected into our controller, to compile the HTML string or the DOM created by jQuery; the resulting function has to be called to link scope and template together.
Therefore we also need to have the $scope
injected.
The original use case contains an ng-repeat
directive, which creates a child scope per each item and adds the corresponding item to it (besides additional properties). Thus we create child scopes from our controller's $scope programmatically to use those with the link function.
private static $inject = ["$scope", "$compile"];
...
private initAngularDependentItem(itemModel: any, template: string): any
{
let itemDOM = $(template);
let itemScope = this.$scope.$new();
itemScope.item = itemModel;
let link = this.$compile(itemDOM);
let compiledAndLinked = link(itemScope);
return compiledAndLinked;
}
A word about child scope:
My item template top-level node uses an ng-controller attribute-directive to assign an itemController per each item. This directive creates new scope.
The ramification of which is that the freshly per item created itemScope
from my code snippet above is actually not the same object that the itemController will know as its very own $scope, but rather the itemController's scope is a child-scope of the object we created. Keep that in mind. I tripped over this when my parent controller was supposed to read properties from the itemScope that the itemController allegedly set before.
We can set a shared object like so itemScope.sharedObject = {}
and it will be the same for both because the childscope inherits it.
Upvotes: 0
Reputation: 66660
You need to use $compile like this:
app.controller(function($scope, $compile) {
var link = $compile(template);
$('...').append(link($scope));
});
Upvotes: 1