Trey Trey - 6 months ago 30
AngularJS Question

Angular JS Directive loaded in expression

I'm new to Angular, and have found a ton of resources about directives and nesting, but can't seem to get this simple example to work. So basically I am working on a tabset, I have an HTML template:

tabset.html


<div>
<ul>
<li ng-repeat="tab in tabset.tabs" ng-class="{active:tabset.current()==$index}">
<a href ng-click="tabset.current($index)">{{tab}}</a>
</li>
</ul>
<div>
<div ng-repeat="pane in tabset.panes">
<div ng-show="tabset.current()==$index">
{{pane.contents}}
</div>
</div>
</div>
</div>


And a search form template:

search-form.html


<div>
<form name="ytSearch" ng-submit="YTCtrl.submit()" novalidate>
<label for="search_box">Search For: </label>
<input id="search_box" ng-model="YTCtrl.searchString"/>
<br>
<label for="location">Location: </label>
<input id="location" ng-model="YTCtrl.location"/>
within
<input type="numeric" value="100" ng-model="YTCtrl.locationRadius" />
<select ng-model="YTCtrl.locationUnit">
<option value="ft">Feet</option>
<option value="m">Meters</option>
<option value="mi">Miles</option>
<option value="km">Kilometers</option>
</select>
<br>
<label for="search_order">Sort By: </label>
<select id="search_order" ng-model="YTCtrl.order">
<option value="relevance">Relevance</option>
<option value="date">Date</option>
<option value="rating">Rating</option>
</select>
<br>
<button id="search">
Search
</button>
</form>
</div>


And a simple app file with 2 directives to handle each of the templates:

app.js


(function() {
angular.module("JHYT", [])
.directive("sidebarTabset", function($compile) {
return {
restrict : 'E',
templateUrl : 'tabset.html',
controller : function($scope, $compile, $http) {
this._current = 0;
this.current = function(i) {
if (i != null)
this._current = i;
return this._current;
};
this.tabs = ['Search', 'Favorite'];
this.panes = [{
contents : "<search-form></search-form>"
}, {
contents : "Favorite Pane"
}];
},
controllerAs : 'tabset',
};
}).
directive("searchForm", function() {
return {
restrict : 'E',
templateUrl : 'search-form.html',
controller : function($scope, $compile, $http) {
this.searchString = '';
this.location = '';
this.locationRadius = '';
this.locationUnit = 'mi';
this.order = 'relevance';
this.submit = function() {
console.log("Submit");
};
},
controllerAs : 'YTCtrl',
}
});
})();


So as you can probably tell, the idea is to be able to send a JSON object into the tabset (through a service probably) and have it build out a dynamic tabset, that actually works exactly as I expected it to. What isn't working is that in the first tab, the content, which is
<search-form></search-form>
is not processed, and the tag is rendered as plain text in the content area.

Since this is a tabset, the "child" doesn't need anything from the "parent", the search form and the tab itself have no scope dependencies. I tried playing with the link and compile functions after seeing some examples of nested structures, but can't seem tog et them to work.

How can I process the content of that variable so that element directives are rendered using their templates?

EDIT:

@sielakos Gave me exactly what I was hoping for, a reusable method for doing this.

I added a directive to my module called compile, which adds a wrapper to allow me to use plain text:

.directive("compile", function($compile){
return {
restrict: 'A',
link: function(scope, element, attr){
attr.$observe("compile", function(str){
var compiled = $compile("<div>"+str+"</div>")(scope);
jQuery(element).replaceWith(compiled);
})
}
}
})


And I changed my tabset to use this directive:

<div>
<ul>
<li ng-repeat="tab in tabset.tabs" ng-class="{active:tabset.current()==$index}">
<a href ng-click="tabset.current($index)">{{tab}}</a>
</li>
</ul>
<div>
<div ng-repeat="pane in tabset.panes">
<div ng-show="tabset.current()==$index">
<div compile="{{pane.contents}}"></div>
</div>
</div>
</div>
</div>

Answer

You will need to compile your string using $compile service if you wish to use it as you would use template. Otherwise it will be treated as normal string and displayed as it is.

Here is example how to use it inside directive:

var compiled = $compile(str)(scope);
element.empty();
element.append(compiled); 

If you wish you can look at this fiddle for more complex example: https://jsfiddle.net/x78uuwp2/

Here I created simple compile directive that takes string compiles it and puts as element body with current scope.

Comments