Is there a way from an AngularJS controller or service to "compile/interpolate/whatever" a small html template which comes with a directive, and get the very final HTML output as a string?
More in details, let's say my template is like this:
var html = '<span my-directive="myVariable"></span>', and my-directive is adding additional html while manipulating myVariable.
Now, I would like to compile that html $compile(html)({myVariable: myVariable}) (not sure if it is the right way) and finally having a full html as a string as final result:
<span my-directive="myVariable">
<span>additional content added by my amazing directive while manipulating myVariable</span>
</span>
Any idea how to achieve this? Any advise is highly appreciated.
Cheers :)
Yes, you can compile HTML with directive (and also provide parameters/variables to that directive) and finally get the rusult as a String
Firstly, let's take a look $compile documentation (Usage section)
We can see there that $compile argument is
Element or HTML string to compile into a template function.
in your case it is var html
and the returned value is
a link function which is used to bind template (a DOM element/tree) to a scope
$compile is returning a function that require scope as an argument
scope is an special object so your {myVariable: myVariable} is not valid, if you want to pass variable to compilation, you have to assign this variable to your current scope scope.myVariable = myVariable and this scope has to be provided as an argument to link function $compile(html)(scope)
Now we must check what is returned by link function:
Calling the linking function returns the element of the template
voila! - we have Element Object so we can get its outerHTML property and assign it to a variable
In Pradeep Plunker you can change
var str = '<div my-directive>Hello world</div>'
var com = $compile(str)(scope);
element.append(com);
to
var str = '<div my-directive>Hello world</div>'
var com = $compile(str)(scope);
console.log('outerHTML',com[0].outerHTML);
element.append(com[0].outerHTML);
to watch results in console :)
NOTE: directive compiled in Plunker is not parametrized by any variable but you can change it of course (just remember that all variables used in compiled directive template have to be assigned to scope you compile with)
$compile compiles an HTML string or DOM into a template and produces a template function, which can then be used to link scope and the template together.
If your requirement is to append a compiled directive into another directive you can do it inside link function.
var str = '<div my-directive>Hello world</div>'
var com = $compile(str)(scope);
element.append(com);
You can use $compile as shown in the above code inside link function.
Example provided at Plnkr Example.
You can use $sce for that:
$scope.variable = $sce.trustAsHtml(yourHtmlString);
And then in your html you need to use:
<div ng-bind-html="variable"></div>
Related
I have a custom directive that generates an input with its validation errors , eventually after building the input, here is the part I'm wondering about :
var html= template.get();
element.html(html);
$compile(element)(scope);
I also added to the directive object which I think is not making difference since I don't have template in the object or should it?
replace:true
but yet the directive element DOM is still there , and the generated input is being appended as a child , can you help me out in this ?
Yes, replace is used in conjunction with template/templateUrl.
For the templates that are retrieved dynamically in link, use
var html= template.get();
element.replaceWith($compile(html)(scope));
Notice that obvious drawback in comparison with replace is that directive's attributes won't be translated to template element, this has to be done manually.
Its not working because you haven't provided a template parameter and your link function was manually adding the elements. Remove the link function and add a template parameter like so and it'll work. Ive updated your fiddle with it working too
app.directive('test',function($compile){
return {
restrict:'E',
replace:true,
template:'<input type="text" name="test">'
}
});
$scope.addNew = function(){
$('.thumb-img-gallary').append("<li><span class='img-del' ng-click='delThumbImgGallaryPhoto($event)'>X</span><img class='thumb-img' src='data:image/jpeg;base64,"+imageData+"'/></li>");
}
I am calling this function to add element dynamically. But then delThumbImgGallaryPhoto() is not getting called.
you cannot just append an element with a ng-click or any other directive, and expect it to work. it has got to be compiled by angular.
explenation about compilation from angular docs:
For AngularJS, "compilation" means attaching directives to the HTML to make it interactive
compelation happens in one of two cases:
When Angular bootstraps your application, the HTML compiler traverses the DOM matching directives against the DOM elements
when you call the $compile service inside an angular context
so what you need to do, is first to compile it(remeber to inject the $compile service in your controller), and then append it:
$scope.addNew = function(){
var elem = $compile("<li><span class='img-del' ng-click='delThumbImgGallaryPhoto($event)'>X</span><img class='thumb-img' src='data:image/jpeg;base64,"+imageData+"'/></li>")($scope);
$('.thumb-img-gallary').append(elem);
}
BTW, remember it is prefable not to have any DOM manipulations done in the controller. angular has directives for that.
You have to compile it with scope
try like this
var item="<li><span class='img-del' ng-click='delThumbImgGallaryPhoto($event)'>X</span><img class='thumb-img' src='data:image/jpeg;base64,"+imageData+"'/></li>"
$('.thumb-img-gallary').append(item);
$compile(item)($scope);
angular doesn't know anything about your newly added element. You need to compile your newly added element using $compile. and you should better use directive for this task.
It is a bad habit to access ui elements from controller.
edit: it would be best using ng-repeat for this task. lets say you have a thumb-gallery directive which is repeated using ng-repeat by thumbs array.
when you need to add a new image you only need to add it to your thumbs array.
it is simple and straightforward
Your html would look like
<thumb-gallery ng-repeat="gallery in galleries"></thumb-gallery>
and your js would look like
var gallery = {};
$scope.galleries.add(gallery);
I try do something with directive in angular, but I've some problem with $compile function in programmatically html element, call here "phe".
var phe = angular.element('<div style="background-color:orange">{{value}}</div>');
When I append "phe" after or before the directive's element, it work like a charm...
var $phe = $compile(phe)(scope);
element.after($phe);
but if I wrapped the directive element with this "phe" the $compile not work.
element.wrap($phe);
Somebody have some idea?
I have made a plunker http://plnkr.co/edit/0x2MmQ7WYmiNog0IEzTj?p=preview
it works if you change the compilation sequence... compile the element before placing it in the dom
var phe_b = angular.element('<div style="background-color:orange"> b {{value}}</div>');
var $phe_b = $compile(phe_b)(scope);
element.before($phe_b);
do same for after...
The reason it doesn't work with wrap is because wrap clones the DOM element. In other words, if you did:
var wrapper = angular.element("<div>");
element.wrap(wrapper);
console.log(wrapper[0] !== element[0].parentNode); // this would output "true"
So, the element that you compiled/linked is not the same that ends up in the DOM.
You could, of course, get the wrapping element (it's the return value of wrap) and $compile it, but you need to be careful not to re-compile/re-link certain directives that were applied on the current element (including the very same directive) and its children.
I've been reading through various post, questions, answers and documentation but haven't managed to solve my problem so far.
I'm using mbExtruder jQuery plugin, and to integrate it within angular, I've created a directive for it:
directive('mbExtruder', function($compile) {
return {
restrict : 'A',
link : function(scope, element, attrs) {
var mbExtruderConfigurationAttrs = scope.$eval(attrs.mbExtruder);
mbExtruderConfigurationAttrs.onExtContentLoad = function() {
var templateElement = angular.element(angular.element(element.children()[0]).children()[0]);
var clonedElement = $compile(templateElement)(scope);
scope.$apply();
var contentElement = angular.element(angular.element(angular.element(element.children()[0]).children()[0]).children()[0]);
contentElement.replaceWith(clonedElement.html());
};
element.buildMbExtruder(mbExtruderConfigurationAttrs);
$.fn.changeLabel = function(text) {
$(this).find(".flapLabel").html(text);
$(this).find(".flapLabel").mbFlipText();
};
I'm extracting the container div, compiling it, applying scope and replacing the original div with the transformed one.
Now, I'm using the ability of mbExtruder to load contents from separate HTML file which looks like this:
<div>
<ul>
<li ng-repeat="property in properties">
<span><img ng-src="../app/img/{{property.attributes['leadimage']}}"/></span>
<a ng-click="openDetails(property)">{{property.attributtes['summary']}}</a>
</li>
<div ng-hide="properties.length">No data</div>
</ul>
</div>
And, in the HTML of the view I have following:
<div id="extruderRight"
mb-extruder="{position: 'right', width: 300, positionFixed: false, extruderOpacity: .8, textOrientation: 'tb'}"
class="{title:'Lista', url:'partials/listGrid.html'}">
</div>
The scope I'm getting in the directive is the scope of the parent controller which actually handles properties array.
The thing is that, if the properties list is pre-populated with some data, that ends up in the compiled HTML - cool. But, any change to properties does actually nothing. I've added watch handler on properties within directive and really, that is triggered whenever any change is made to properties, but, ng-repeat does not pick that up. The original idea is to have properties list empty in the beginning - that causes ng-repeat compile to have just this output:
<!-- ngRepeat: property in properties -->
Is this doing a problem? The fact that ng-repeat declaration has actually disappeared from DOM.
Am I missing something obvious here? I've read the documentation on $compile and ng-repeat and I would say that I don't need to manipulate DOM by myself, ng-repeat should do its work. Or I'm totally wrong?
Thanks.
EDIT: Plunker
You're passing in
contentElement.replaceWith(clonedElement.html());
Notice here that you're essentially replacing it with raw HTML string code. Hence, Angular has no concept of the directives inside the code. However, I don't see why this is needed at all. Just compiling and attaching the result to scope seems to work just fine:
var templateElement = angular.element(angular.element(element.children()[0]).children()[0]);
$compile(templateElement)(scope);
Here's the updated, working version:
http://plnkr.co/edit/gxPAP43sx3QxyFzBitd5?p=preview
The documentation for $compile didn't say too much, but $compile returns a function, which you then call with the scope you want to attach the element to (as far as I've understood). Hence, you don't need to store the reference to the element at all, unless you want to use it later.
I'm build an Angular.js app that uses a third-party library. The library requires me to pass in a string containing HTML. This HTML is complex and requires several values to be injected. I'd like to use Angular's build in $compile service to compile that data. Here's an example:
// create the template
var template = "<p>{{ test }}</p>";
// set up the scope
var scope = $rootScope.$new();
scope.test = "hello";
// compile the template
var htmlString = $compile(template)(scope)[0].outerHTML;
When I run this code, I would expect htmlString to be <p>hello</p>, but instead it's <p class="ng-scope ng-binding">{{ test }}</p>. I understand Angular is setting up its bindings, but I want static content. Is there any way to achieve the behavior I want?
As sza said, the solution is easy thanks to the $interpolate function:
var template = 'Hello {{ name }}';
console.log($interpolate(template)({name: 'World'}));
Console output is:
Hello World