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);
});
Related
I have a text field that is being controlled by jQuery/external Javascript. I'm looking to add something that causes the text field to also be shown on another element. I'm looking to develop something like this but I know this isn't how you program it:
<span id="reading" ng-bind="reading"></span>
<div ng-bind="reading"></div>
<script>
$('#reading').text('100');
</script>
The desired output is to have 100 displayed in both the span and the div.
So rather that going for jQuery method to change the text, I'd change the reading scope value.
$scope.reading = '10'
But I know you wanted to call that method from external context of angular, so below is the way by which you could access the scope by having DOM query on ng-controller name .
var scope = angular.element('[ng-controller=ctrl]').scope();
scope.reading = '10'
scope.$apply(); // to update bindings.
If you want angular to detect your changes. you should do that in $apply(). You have to create a directive on those elements. inside that directive write a link function
link:function(scope,ele,attrs,ngModel){
scope.$apply(update); //trigger digest cycle
function update() {
ngModel.$setViewValue(100); //changes model
ngModel.$render(); //updates view
}
}
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">'
}
});
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)
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.
I created a custom directive in angular so that I can fade out a form on submit and replace it with a template with a custom message.
The desired workflow is as follows:
The user completes the form and clicks submit.
The controller updates the model with a new object and emits a "formSubmitted" event with some args.
The directive listens for the event and fades out the form.
The directive loads a partial html filled with the args from the event.
I resolved the first 3 steps, but I wasn't able to get around the final step (I don't want to hardcode the html as Strings, I want to pull it from another file if possible).
How can it be done?
Edit: some sample code (simplified):
This is the form:
<section class="hero-unit {{containerClass}}" tk-jq-replace-children>
<h2>{{formTitle}}</h2>
<form class="form-search" name="solform" novalidate>
<fieldset>
...
This is the controller:
if( formWasSavedOk ){
$scope.formSubmittedMsg = msg;
//Here emits the custom event
$scope.$emit( 'solicitud:formSubmitted' );
}
This is the directive:
.directive( 'tkJqReplaceChildren', function( $compile ){
return {
link: function( $scope, $iElement/*, $iAttrs*/ ){
//The directive listens for the event
$scope.$on( 'solicitud:formSubmitted', function( /*event*/ ){
$iElement
.children()
.fadeOut(1000)
.promise()
.done(function(){
var $detachedElments = $(this).detach();
//The html is compiled and added to the DOM
$iElement.html( $compile( "<h2>{{formSubmittedMsg}}</h2>" )($scope) );
$scope.$apply();
});
});
}
};
});
<h2>{{formSubmittedMsg}}</h2> is the code I want to pull from app/partials/create/createdOk.html (it is way more than just a header, that's why I want to load it from a file)
I'm not sure if you are looking for the $http service. I have created a plunker http://plnkr.co/edit/13kFLh9RTsIlO4TaFIFQ?p=preview, which doesn't cover the first three steps, but covers the 4th step you need.
In the plunker click on the text "Click here to submit the form", and notice the new text is is inserted. This new text is from the external file called tmpl.html. In the firebug console, you can notice a get call after you clicked the text, to fetch the tmpl.html
I believe the "Angular way" to fetch an external html snippet would be to use the ng-include directive:
<ng-include
src="{string}"
[onload="{string}"]
[autoscroll="{string}"]>
</ng-include>
As for why your directive didn't work, my guess is that you're fetching the html at the directive's link phase, rather than compile phase. There's a nice explanation on the difference on this answer, but it's pretty much this:
If you are going to make DOM transformation, it should be compile if
you want to add some features are behavior changes, it should be in
link.
I would recommend moving most of that logic away from the directive, to the controller: fetching the data using a $resource or $http service, and then passing the results to the newly created scope of the ng-directive.