I have a directive, where I'm trying to dynamically load different partials depending on an object that is injected into directive
function countSummary() {
var directive = {
scope: {
countData: '='
},
link: link,
template: '<div ng-include=\'countData.countType === "expected" ? ' + '"/app/count/countsummary/countExpected.html" :' +
'"/app/count/countsummary/countBlind.html"\'>' +
'</div>'
}
return directive;
function link(scope, element, attrs) { ... }
}
I'm using grunt-html2js to convert all html files to be added to $templateCache. I have verified that the html file is in added to $templateCache, however when I load the page it is having difficulty finding only the .html files that are referenced in the template function.
Is this a timing issue of any sort? Is there a better way to use the template function?
ng-include argument needs to evaluate to URL. I'd do the following, which will be dynamic as the scope variable changes (using the ng-if directive will conditionally switch between views):
function countSummary() {
var directive = {
scope: {
countData: '='
},
link: link,
template: '<div ng-if="countData.countType === \'expected\'" ng-include="\'/app/count/countsummary/countExpected.html\'"></div>' +
'<div ng-if="countData.countType !== \'expected\'" ng-include="\'/app/count/countsummary/countBlind.html\'"></div>'
}
return directive;
function link(scope, element, attrs) { ... }
}
An alternative way of doing this, which opens up a lot more options, is to compile in the link function:
<script type="text/ng-template" id="my_template_1">
<div ng-if="countData.countType === 'expected'" ng-include="/app/count/countsummary/countExpected.html"></div>
<div ng-if="countData.countType !== 'expected'" ng-include="/app/count/countsummary/countBlind.html"></div>
</script>
function link(scope, element, attrs) {
var html = $templateCache.get('my_template_1');
// parse HTML into DOM element
var template = angular.element( html );
// compile the template
var linkFn = $compile(template);
// link the compiled template with the scope
var el = linkFn(scope);
// append to DOM
element.appendChild(el);
}
Related
I have a custom directive that simply $compiles a template into another.
.directive('staticInclude', function($http, $templateCache, $compile) {
return function(scope, element, attrs) {
var templatePath = attrs.staticInclude;
//
$http.get(templatePath, {
cache: $templateCache
}).success(function(response) {
var contents = element.html(response).contents();
$compile(contents)(scope);
});
};
});
I use it like:
<div static-include="components/campaign/details.html"></div>
Because I'm using aliases for the controller (using angular UI router), all model in any of the templates are like:
<p>Delivery Time: <span class="text-medium">{{CtrlAlias.campaign.newsletter.sentDate | date:CtrlAlias.currentUser.params.settings}}</span></p>
How do I make this directive work in multiple templates where CtrlAlias changes?
I tried changing $compile(contents)(scope); into $compile(contents)(scope.newCtrlAlias);
Any ideas?
When you $compile and then link, you are free to provide your own scope against which the compiled content is linked. That means that you can have the template content refer to some arbitrary ViewModel name, say vm:
<p>Delivery Time: <span>{{vm.campaign.newsletter.sentDate}}</span></p>
And link against a scope that has vm property:
var scope = { vm: {...} }
It actually might be even useful to use an isolate scope for your compiled content, to make sure that you aren't assuming an existence of scope variables that may or may not be there when the content is linked:
.directive('staticInclude', function($templateRequest, $compile) {
return {
link: function(scope, element, attrs){
var alias = attrs.alias || 'vm';
var templatePath = attrs.staticInclude;
var newScope = scope.$new(true); // isolate scope
newScope.vm = scope[alias];
// $templateRequest is essentially $http with $templateCache
$templateRequest(templatePath)
.then(function(html){
$compile(html)(newScope, function cloneAttachFn(clone){
element.empty();
element.append(clone);
});
});
}
};
});
Then usage is like so:
<div ng-controller="MainCtrl as main">
<div static-include="components/campaign/details.html" alias="main">
</div>
</div>
Really not sure I understand why you would need to use this so it's not easy to answer. However, one possible solution could be to wrap the template in a <div> to which you can append the desired controller information. It's a bit gross but it might work for you. You would have to pass in the controller name and it's alias but you could perhaps add that to your $state's data properties and access them from that but again it all seems a bit hacky.
DEMO
app.directive('staticInclude', function($http, $templateCache, $compile) {
return {
scope: {
ctrlName : '#',
alias : '#'
},
link: link
};
function link(scope, element, attrs) {
var templatePath = attrs.staticInclude;
$http
.get(templatePath, {
cache: $templateCache
})
.success(function(response) {
var ctrlStr = scope.ctrlName + ' as ' + scope.alias,
template = '<div ng-controller="' + ctrlStr + '" >' + response + '</div>',
contents = element.html(template).contents();
$compile(contents)(scope);
});
};
});
I'm making a directive for a States Select in angular. It's working, but I spent a while trying to figure out a way to compile the template before it's in the DOM. It currently works like this:
app.register.directive('stateDropdown', ['StatesFactory', '$compile', function (StatesFactory, $compile) {
function getTemplate(model) {
var html = '<select ng-model="' + model + '" ng-options="state.abbreviation as state.name for state in states" class="form-control"></select>';
return html;
}
function link (scope, element, attrs) {
scope.states = StatesFactory.States;
element.html(getTemplate(attrs.stateModel));
$compile(element.contents())(scope);
}
return {
replace: true,
link: link
}
}]);
But as such it inserts the template into the element THEN compiles it against scope. Is there a better way to do this? Such as compiling the template before it's even inserted?
Scratch what I had before.
[Edit 2]
Using a dynamic model is a bit problematic trying to fit it into the normal Angular workflow.
Instead you will need to compile the template in the directive by hand but add the ng-model before doing so, You will also need to manage the replacement of the existing element with the built template.
module.directive('stateDropdown', function (StatesFactory, $compile) {
var template = '<select ng-options="state.abbreviation as state.name for state in states" class="form-control"></select>';
return {
scope: true,
controller: function($scope) {
$scope.states = StatesFactory.states;
},
compile: function($element, $attrs) {
var templateElem = angular.element(template).attr('ng-model', '$parent.' + $attrs.stateModel);
$element.after(templateElem);
$element.remove();
var subLink = $compile(templateElem);
return {
pre: function(scope, element, attrs) {
subLink(scope);
},
post: function(scope, element, attrs) {
}
}
}
};
});
A working example of this can be found here: http://jsfiddle.net/u5uz2po7/2/
The example uses an isolated scope so that applying the 'states' to the scope does not affect existing scopes. That is also the reason for the '$parent.' in the ng-model.
I have a custom markdown directive that works fine. Now I would like to use a custom youtube directive within the content that is loaded through that markdown directive. The youtube directive on its own works fine, but as soon as I put it within the markdown file, it gets ignored by angular.
The following works fine (but is not what I want to do):
.html
<div markdown link="contentfile.md">
</div>
<div my-youtube code="'videoidhere'"></div>
.md
markdown content here
The following is what I want to do (but does not work):
.html
<div markdown link="contentfile.md">
</div>
.md
markdown content here
<div my-youtube code="'videoidhere'"></div>
more markdown content here
In the second case, it seems as if the YouTube directive is never even called.
What do I need to do to tell angular to evaluate that directive, after the markdown directive was evaluated?
For completeness, here are the directives:
markdown:
app.directive( 'markdown', function( $http ) {
var converter = new Showdown.converter();
return {
restrict: 'A',
scope: { link: '#' },
link: function ( scope, element, attrs )
{
attrs.$observe('link',function(link)
{
$http.get('modules/test/files/' + link).success(function(response)
{
var htmlText = converter.makeHtml(response);
return element.html(htmlText);
});
});
}
};
});
youtube:
app.directive('myYoutube', function( $sce ) {
return {
restrict: 'EA',
scope: { code:'=' },
replace: true,
template: '<div style="height:400px;"><iframe style="overflow:hidden;height:100%;width:100%" width="100%" height="100%" src="{{url}}" frameborder="0" allowfullscreen></iframe></div>',
link: function (scope) {
scope.$watch('code', function (newVal) {
if (newVal) {
scope.url = $sce.trustAsResourceUrl("http://www.youtube.com/embed/" + newVal);
}
});
}
};
});
You do add to the DOM to the .md file using the markdown directive, but as it isn't compiled, angular does not register it.
Something like this should work:
$http.get('modules/test/files/' + link).success(function(response)
{
var htmlText = converter.makeHtml(response);
element.html(htmlText);
$compile( element.contents() )( scope );
return element;
});
I am trying to call a html page which is given in the templateUrl of my directive when I click a button, below is my code "hi" should be displayed when I click the "click me" button. Please suggest me how to do this.
sample.html:
<div ng-controller="MyController">
<button custom-click="">Click Me</button>
</div>
sample.js:
appRoot.directive('customClick', function() {
return {
link: function(scope, element, attrs) {
element.click(function(){
templateUrl:'/page.html';
});
}
}
});
Page.html:
<div><h4>HI</h4></div>
Update: The Snippet has been updated with getting the code from a URL
Adding onto the above answers:
appRoot.directive('customClick', function($http, $compile) {
return {
link: function(scope, element, attrs) {
element.click(function(){
$http.get("/page.html").then(function(resp){
$(element).html(resp.data);
var fnLink = $compile(element);
fnLink($scope);
});
});
}
}
});
P.S: Needs jQuery to run as using some functions like html() which can be bypassed if you dont want to include jQuery
I don't think that structure is possible, at all.
The easiest way would be to handle a show/hide type of functionality on the directive and have the template be there at all times.
For this you could use either ng-show, ng-hide or ng-if (and some others that I won't dig into).
base directive
appRoot.directive('customClick', function () {
return {
template: '<div><h5>HI</h5></div>',
link: function (scope, el, attrs) {
scope.active = false;
el.on('click', function () {
scope.$apply(function () {
scope.active = !scope.active;
});
});
}
}
});
ng-show
template: '<div ng-show="active"><h5>HI</h5></div>'
ng-hide
template: '<div ng-hide="!active"><h5>HI</h5></div>'
ng-if
template: '<div ng-if="active"><h5>HI</h5></div>'
Edit: If you are using templateUrl, simply put the ng-show/hide/if directive on the root element of the template being referenced, and this should work the same.
Oh, and here's a fiddle.
http://jsfiddle.net/ADukg/5426/
I'm trying to build a "select" style directive that allows a template to be provided for each option but I'm struggling to get it working. Here's what I have so far:
ngApp.directive('mySelect', function ($compile) {
function link(scope, element, attrs) {
var template = element.html();
var selectedItemTemplate = template.replace("item", "selectedItem");
var html = "<div class='mySelect_selectedItem'>" + selectedItemTemplate + "</div>";
html += "<div ng-repeat='item in items'><div class='mySelect_item' ng-click='onItemClicked(item)'>";
html += template;
html += "</div></div>";
element = element.replaceWith($compile(html)(scope));
scope.onItemClicked = function (item) {
scope.selectedItem = item;
};
scope.selectedItemContainer = element.find(".mySelect_selectedItem");
scope.itemContainers = element.find(".mySelect_item");
scope.itemContainers.hide();
};
return {
link: link,
restrict: 'E',
scope: {
selectedItem: '=',
items: '=',
},
}
});
And usage would be something like:
<my-select items="things" selected-item="selectedThing">
<div>ITEM NAME: {{item.name}}</div>
</my-select>
Although on render I am getting what I would expect from my template, I can't seem to access the UI elements after the $compile, so the two jQuery selectors don't work. I am guessing I need to call the $compile method somewhere else, or perhaps there is a much easier way of doing this?
Plunker here: http://plnkr.co/edit/q7DevKOm3tpgmvYpjqlR?p=info