I am trying to load a html template with ng-repeats in it and then use the $compile service to compile it and use the compiled html in an email.
The problem.... Ok before asking let me set terminology...
binding placeholder: {{customer.name}}
binding value: 'john doe'
Using $interpolate i get the actual binding values but does not work with ng-repeats.
Example:var html = $interpolate('<p>{{customer.name}}</p>')($scope)
Returns: '<p>john doe</p>'
Ng-repeats do not work
Using $compile i get the bindings placeholders ie {{customer.name}} but what I need is the binding value 'john doe' .
Example: var html = $compile('<p>{{customer.name}}</p>')($scope)
Returns: '<p>{{customer.name}}</p>'
Once I append to a page I see the binding values. But this is for email not for a page plus it has ng-repeats that $compile can compile
How can I create a dynamic email template that after compiling it, it returns html with binding values and not just the binding placeholders so I can send it as email?
Using $compile is the right way to go. However, $compile(template)($scope) doesn't yield the interpolated HTML that you expect right away. It just links the compiled template to a scope to be interpolated during the next $digest. To get the desired HTML, you need to wait for that interpolation to happen, like so:
var factory = angular.element('<div></div>');
factory.html('<ul ng-repeat="...">...</ul>');
$compile(factory)($scope);
// get the interpolated HTML asynchronously after the interpolation happens
$timeout(function () {
html = factory.html();
// ... do whatever you need with the interpolated HTML
});
(working CodePen example: http://codepen.io/anon/pen/gxEfr?editors=101)
Related
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>
$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);
In my application, users are given the option to send a confirmation message. This message is pregenerated with a default value (which depends on some other context), but should be editable by the user.
In angularJS, the intent is that the pregenerated messages comes from a template. So for instance I would have Msg1.html and Msg2.html, each of which includes MsgCommon.html (presumably using ng-include). The resulting file is then interpolated with the current scope to provide the text version of the pre-generated message, which I would presumably put in a textarea.
But I haven't found how to do this with angularJS. AngularJS rightly does not process the contents of , so I can't put a ng-include in there. And presumably there should be a ng-model on the textarea, so I would need to initialize in my controller the model element after processing the template. The following almost works:
$http.get('msg1.html', {cache: $templateCache}).then(function(resp) {
$scope.msg = $interpolate(resp.data)($scope);
});
in html:
<textarea ng-model='msg'></textarea>
This correctly interpolates strings like {{...}} but will not process directives like ng-repeat or ng-include. I guess I need to use some form of $compile instead, but this expects a DOM string or element, and my message is not HTML.
Any ideas on how this could be done ?
This solution is for demonstration purposes only. I do not recommend to use this in a real world scenario. I think it is better to use just $interpolate and create the necessary logic right in your controller. (ie. Instead of using ng-repeat you could concatenate your list in your controller and interpolate this string)
The idea of this ugly solution is to compile a template and the use the innerText of the element to get the plain text only (Plunker):
$templateRequest('msg1.html').then(function(resp) {
var el = angular.element('<div></div>');
var templateScope = $scope.$new();
templateScope.name = 'Foo'
templateScope.tasks = ['t1', 't2', 't3', 't4'];
el.html(resp);
$compile( el.contents() )( templateScope );
setTimeout(function(){
$scope.msg = el[0].innerText;
$scope.$apply();
}, 10);
});
I'm writing an AngularJS app that gets a list of posts from a server, and then uses an ngRepeat and a custom post directive to output all the posts.
Part of the post object is a blob of html, which I currently add to the directive by first doing an $sce.trustAsHtml(blob), and then using the ng-bind-html directive and passing the trusted html blob to it. It works fine, but now I want to modify the html before adding it to the output. For instance, I want to find all link tags and add a target="_blank" to it. I also want to remove any content editable attributes from any element. etc.
What is the best way of doing this? I was thinking of just loading it up in a document fragment and then recursively iterating through all of the children doing what I need to do. But I assume there is a better AngularJS way to do this?
EDIT:
here is a codepen with an example of what I have:
http://codepen.io/niltz/pen/neqlC?editors=101
You can create a filter and pipe (|) your content through it. Something like:
<p ng-bind="myblob | myCleanupFilter">
Your myCleanupFilter would look something like this (not tested):
angular.module('myApp').filter('myCleanupFilter', function () {
return function cleanup (content) {
content.replace('......') // write your cleanup logic here...
};
});
If you want to add attributes that are themselves directives, then the best place to add them is in the compile function in a custom directive.
If they are just plain old attributes, then there's nothing wrong with hooking into DOM ready in your run block, and adding your attributes with jquery.
var app = app.module('app',[]);
app.run(function ($rootScope){
$(document).ready(function()
$rootScope.$apply(function(){
$('a').attr('title','cool');
});
})
});
If you want add the attributes after the compile phase but before the linking phase in the angular life cycle then a good place to do it is in the controller function for a directive that's placed on the body element.
<body ng-controller="bodyCtrl">
</body>
app.controller('bodyCtrl', function($element){
$('a', $element).attr('title','cool');
});
During the compile phase angular will walk the DOM tree, matching elements to directives, and transforming the HTML along the way. During the link phase, directives will typically set up watch handlers to update the view when the model changes. By placing a directive on the body element, it ensures that all directives have been compiled, but the linking phase hasn't started yet.
I want to modify web pages' behavior using angularjs and greasemonkey. I want to know, what's the best way to do it? Should I use jquery to inject attributes like "ng-*" to DOM elements before I can write some angular code? Or can I solely stick to angularjs?
Thanks.
There's a general answer about dynamically modifying AngularJS content in the DOM from JavaScript code here:
AngularJS + JQuery : How to get dynamic content working in angularjs
To sum up, when you put ng-* attributes into the DOM from JavaScript code, they won't automatically get hooked up; but AngularJS provides the $compile function for hooking up new HTML content with AngularJS attributes from JavaScript.
So what does this mean when it comes to Greasemonkey/Userscript?
For the purposes of this I'm assuming that your Greasemonkey script is modifying an existing page that already uses AngularJS, and the AngularJS content you want to add uses some of the variables or functions in AngularJS scopes already on that page.
For those purposes:
Get a reference to $compile from AngularJS' dynamic injection system
Get a reference to the AngularJS scope that you want your HTML code to be connected to
Put your HTML code with ng-* attributes in a string and call $compile on it and the scope.
Take the result of that and put it into the page using the usual jQuery-style ways.
To illustrate, here's a little script for CERN's Particle Clicker game, which adds a stat under the 'workers' section.
$(function () { // Once the page is done loading...
// Using jQuery, get the parts of the page we want to add the AngularJS content to
var mediaList = $('ul.media-list');
var medias = $('li.media', mediaList);
// A string with a fragment of HTML with AngularJS attributes that we want to add.
// w is an existing object in the AngularJS scope of the
// <li class="media"> tags that has properties rate and cost.
var content = '<p>dps/MJTN = <span ng-bind="w.rate / w.cost * 1000000 | number:2"></span></p>';
// Invoke a function through the injector so it gets access to $compile **
angular.element(document).injector().invoke(function($compile) {
angular.forEach(medias, function(media) {
// Get the AngularJS scope we want our fragment to see
var scope = angular.element(media).scope();
// Pass our fragment content to $compile,
// and call the function that $compile returns with the scope.
var compiledContent = $compile(content)(scope);
// Put the output of the compilation in to the page using jQuery
$('p', media).after(compiledContent);
});
});
});
** NB: Like any AngularJS function that uses its dependency injection,
.invoke uses the parameter names of the function you pass to it
determine what to inject, and this will break if you're using a minifier that changes the parameter names.
To avoid this you can replace
.invoke(function($compile) { ... });
with the form
.invoke(['$compile', function($compile) { ... }]);
which won't break if the minifier changes the parameter name to something other than $compile.