Angular.js's $compile doesn't replace values - angularjs

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

Related

AngularJS compile html with directive and get output as string?

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>

How can I debug a template with directive?

I am new to AngularJS debug. I wonder if anyone can show me an example about how to debug a template with directive like ng-repeat, ng-if, etc. What I need to see is how those directive initialized and how those variable been given and render, kinda like step debug in template.
I can set breakpoint if I define the controller, but when it comes AngularJS native directives and template, I have no idea how to do that
The best way will be to have your local copy of Angular JS script ( dont use min version - use full version)
Then in chrome, you can use debugger function or just use normal breakpoint in that AngularJS script.
Example :
var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
var NG_REMOVED = '$$NG_REMOVED';
var ngRepeatMinErr = minErr('ngRepeat');
var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) {
debugger; // Set the debugger inside
// this function
....

AngularJS: setting value of textarea from template

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);
});

Angular $compile template for dynamic email

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)

How to use angularjs with greasemonkey to modify web pages?

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.

Resources