$compile return html in object - javascript

so im calling a html template and want to bind the data with angular, so i get the data to bind, i get the html, when i try to compile it will return all the html binded but in (i think) object, what can i do to make it html.
This is the code
$.get("file.html", function(partial){
var scope = $rootScope.$new();
scope.data = result;
var el = angular.element(partial);
var compiled = $compile(el)(scope);
var finalHtml = el[0];
$timeout(function(){
var calendar = window.open();
calendar.document.write(finalHtml);
calendar.focus();
calendar.print();
});
});
I already try .html .toString String() nothing worked
Thank you in Advance

Your compiled variable is an angular jQuery or jqlite element that can be inserted into your document. If you want to get the html for it, you can use use the outerHTML attribute on the underlying node (you get the underlying node by grabbing the first array element compiled[0]) - https://developer.mozilla.org/en-US/docs/Web/API/Element/outerHTML
var compiled = $compile(el)(scope);
// scope.$digest() // only call if not within an angular $digest already
$timeout(function() {
var finalHtml = compiled[0].outerHTML;
...
}
According to the documentation "After linking the view is not updated until after a call to $digest which typically is done by Angular automatically." so you either have to manually call scope.$digest() or actually use one the angular API to do the request using either $http or preferably using $templateRequest like #ThinkingMedia suggested. After the angular $digest has run, then you can access the updated view.
I created a plunker here that shows how it all works properly using just the AngularJS api: http://plnkr.co/edit/rFcfgB3FWhsfyySfr0rU?p=preview
I also changed how the popup is opened a bit to deal with the security implication of doing popups.

Related

Creating 2-way binding for elements created after initial compiliation

I created a directive that dynamically creates a form based on a json from the server. I'm trying to add ng-model attribute to the various input elements so that I'll be able to use the input values after the user has typed them in and clicked submit. The ng-model attribute seems to be added but 2-way databinding doesn't work.
EDIT: I'm calling buildForm from within the link function as seen below:
function link(scope, elem, attr, ctrl) {
//asyc request to the server, data here is a json object from the server
getMovieDataStructure({
onSuccess: (data) => {
scope.mdb = data;
buildForm(scope.mdb, elem);
},
onFail: (res) => {
console.log("ERROR getting it");
}
});
}
Here is some of the code from in the directive:
//mdb is an array of objects describing the form requirments
function buildForm(mdb, formElement) {
for(var i=0; i < mdb.length; i++) {
if(mdb[i].type == 'string') {
if(mdb[i].maxLength && mdb[i].maxLength > 1024) {
//if maxLength > 1024 put a text area instead
formElement.append(createTextArea({
id: mdb[i].fieldName,
placeholder: mdb[i].fieldName
}));
} else {
//add input field to the form
formElement.append(createTextInput({
id: mdb[i].fieldName,
placeholder: mdb[i].fieldName
}));
}
} else if(){
//some more cases
}
formElement.append("<br>");
}
//...some more code...
}
//one of the functions to create an input element
function createTextInput(data) {
var elem = angular.element("<input>");
elem.attr("type", "text");
elem.attr("id", data.id);
elem.attr("ng-model", data.id);
elem.attr("placeholder", data.placeholder);
return elem;
}
For example, a result of an input element on the html page could look like this:
<input placeholder="movie_name" ng-model="movie_name" id="movie_name" type="text"> </input>
And if I'll put the same tag directly to in the html file the 2-way binding works great.
What am missing here? Is there a better way to do this and I'm just overcomplicating things?
Somewhere after you update the form you will need to call $compile, otherwise angular will not be aware of your changes. See:
https://docs.angularjs.org/api/ng/service/$compile
Something to try would be to call $rootScope.apply() after you call the buildform method maybe. What may be happening is that you are making all these changes to the DOM after the digest cycle completes and angular won't know about your changes until the next cycle happens.
So in your case it will be:
buildForm(scope.mdb, elem);
scope.$apply();
Thing is digest loop needs to be called explicitly in your case cause angular is unaware of the change made.
USE:
buildForm(scope.mdb, elem);
scope.$apply();
OR
But there is a better way for using $apply:
scope.$apply(buildForm(scope.mdb,elem));
The difference is that in the first version, we are updating the values outside the angular context so if that throws an error, Angular will never know.
As wdanda mentioned, since the directive adds DOM elements, it needs to be compiled afterwards to let angular be aware of the changes
Short answer is that the line buildForm(scope.mdb, elem); has been changed to $compile(buildForm(scope.mdb, elem).contents())(scope); and '$compile' was added to the directive's list of dependencies.
Long explanation:
buildForm(scope.mdb,elem) returns the element of the directive (so actually adding $compile(elem.contents())(scope); after buildForm would be equivilant), .contents() on an angular wraped element returns all of that element children.
That means that $compile(buildForm(scope.mdb, elem).contents()) tells angular to compile all the children of the directive's element, after buildForm has added some elements to it (and which some of them have directives of their own.
The call for .contents() is important because:
we only compile .childNodes so that we don't get into infinite loop compiling ourselves
(from https://docs.angularjs.org/api/ng/service/$compile)
The $compile() function returns a linking function that needs to be called with a scope to link to. So adding (scope) at the end will call that returned function.
A more clear (though slightly less elegant) way to write that code, would be:
var element = buildForm(scope.mdb, elem); //buildForm returns an angular wraped element
var linking = $compile(element); // $compile returns a linking function
linking(scope); //linking is functions that takes a scope object
//and needs to be run after compilation

Getting access to angular $scope from own event

$I have a custom javascript object, that can fire events.
I would like to access the angular $scope inside the event-handler, but I have read somewhere that using angular.element(...).scope() is not good, because it's only meant for testing.
My other idea was to register the handle on my object inside the controller, but this is not working (looks like $scope.somevalue gets set, but I don't think $scope is the same object).
I have found many answers here on Stack Overflow for similar questions, but they all seem to be using directives. All I want is to get a value from the object when it's updated, and display it.
Here are the two ways I have tried.
var myObj = GetMyObjInstance();
// Working, but apparently it's not good practise to call .scope() on an element.
myObj.onUpdated = function(){
console.log("myObj updated");
var v = myObj.getValue();
var controllerDiv = document.getElementById("controller");
var $scope = angular.element(controllerDiv).scope();
$scope.apply(function(){
$scope.someValue = v;
});
}
// Tried to do this, thinking i would get closure on the scope.
angular.module('myApp', []).controller('controller', function($scope){
myObj.onUpdated = function(){
console.log("myObj updated"); // Gets logged to console...
var v = myObj.getValue();
$scope.somevalue = v; // ... but somevalue does not get displayed.
$scope.apply(); // Error, says it's not a function, so maybe this is not the right object?.
}
});
Use AngularJS directives to handle events and update scope.
app.directive("xdEvent", function() {
return linkFn(scope, elem, attrs) {
elem.on("event", function(e) {
scope.$eval(attrs.xdEvent, {$event: e});
scope.$apply();
});
};
};
USAGE
<div xd-event="fn($event)"></div>
I think using a Service instead of a controller is a better practice. You can call a service from outside javascript with the injector like explained in this thread :
Call angularjs service from simple js code
If it is still important for you to access this variables from controller, you can use $watch to tell your controller to update itself when the service variables change.
Hope this help.
A+

AngularJS: Using $compile on html that contains directives with templateurl

I have a legacy application that has some content inserted into the DOM via jQuery. I would like the legacy parts of the codebase to be responsible for compiling the html that it inserts into the DOM.
I can get it to compile the initial html using $compile, but any DOM elements added by a directive's template or templateUrl are not compiled, unless I call $scope.$apply() from within the directive itself.
What am I doing wrong here?
Link to fiddle: http://jsfiddle.net/f3dkp291/15/
index.html
<div ng-app="app">
<debug source='html'></debug>
<div id="target"></div>
</div>
application.js
angular.module('app', []).directive('debug', function() {
return {
restrict: 'E',
template: "scope {{$id}} loaded from {{source}}",
link: function($scope, el, attrs) {
$scope.source = attrs.source
if( attrs.autoApply ) {
// this works
$scope.$apply()
}
},
scope: true
}
})
// mimic an xhr request
setTimeout(function() {
var html = "<div><debug source='xhr (auto-applied)' auto-apply='1'></debug><br /><debug source='xhr'></debug></div>",
target = document.getElementById('target'),
$injector = angular.injector(['ng','app']),
$compile = $injector.get('$compile'),
$rootScope = $injector.get('$rootScope'),
$scope = angular.element(target).scope();
target.innerHTML = $compile(html)($scope)[0].outerHTML
// these do nothing, and I want to compile the directive's template from here.
$scope.$apply()
$scope.$root.$apply()
angular.injector(['ng','app']).get('$rootScope').$apply()
}, 0)
output
scope 003 loaded from html
scope 005 loaded from xhr (auto-applied)
scope {{$id}} loaded from {{source}}
Update: Solution works for directives with a template property, but not templateUrl
So, I should have been compiling dom nodes, not an HTML string. However, this updated fiddle shows the same failing behavior if the directive contains a templateUrl:
http://jsfiddle.net/trz80n9y/3/
As you probably realised, you need to call $scope.$apply() for it to update the {{bindings}} from the scope values.
But the reason you couldn't do it inside your async function was that you were compiling the HTML against the existing scope for #target, but then trying to append just the HTML. That won't work, because you need to have the compiled node in the DOM, either by appending the entire compiled node using jQuery's .append() or similar, or by setting the DOM innerHTML first, then compiling the node that is in the DOM. After that, you can call $apply on that scope and because the directive is compiled and in the DOM, it will be updated correctly.
In other words, change your async code as follows.
Instead of:
target.innerHTML = $compile(html)($scope)[0].outerHTML
$scope.$apply()
Change it to:
target.innerHTML = html;
$compile(target)($scope);
$scope.$digest();
Note that I did a $digest() instead of $apply(). This is because $apply() does a digest of every single scope, starting from the $rootScope. You only need to digest that one scope you linked against, so it is sufficient (and faster, for any reasonably sized app with lots of scopes) to just digest that one.
Forked fiddle
Update: Angular can compile strings and detached DOM nodes
I just checked, and the OP was actually correct in assuming that Angular can compile strings of HTML or detached DOM nodes just fine. But what you do need to do is make sure you actually append the compiled node to the DOM, not just the HTML. This is because Angular stores things like the scope and the binding information as jQuery/jQueryLite data on the DOM node*. Thus you need to append the whole node, with that extra information, so that the $digest() will work.
So an alternative way of having this work is to change the same portion of the OP's code as above to:
target.appendChild($compile(html)($scope)[0]);
$scope.$digest()
* Technically, it is stored in the internal jQuery data cache, with the cache key being stored on the DOM node itself.
Append the element to the target first, then compile it.
html = angular.element(html);
target = angular.element(target);
target.append(html);
html = $compile(html)($scope)
http://jsfiddle.net/f3dkp291/16/

is angular's compile function async?

I have a service in which I call $compile to compile my template. Function in JS are getting executed one after another. However, in order get my final HTML I have to put html() in timeout callback. Otherwise, I get my template with {{ placeholders }} only. The questions is why I need to use timeout here? Here is my code:
var newScope = $rootScope.$new(true);
angular.extend(newScope, data);
var compiled = $compile(template);
var linked = compiled(newScope);
$timeout(function () {
def.resolve(linked.html());
});
No, $compile is not asynchronous. Calling $timeout is necessary because of the nature of the browser and the relationship between JavaScript and the DOM. The $timeout allows the DOM to be updated so that your call to .html() actually has html to return.
Your example is correctly using $compile and $timeout.
A couple of notes:
$timeout already returns a promise which is resolved with the return value of the passed function, so you could simply do this:
return $timeout(function(){
return linked.html();
});
As you have it, the outermost (root) element of your template gets thrown away because .html() returns the innerHTML of that element. If you need to preserve that root element you can wrap it in a div first like so:
return $timeout(function(){
return angular.element('<div/>').append(linked).html();
});
Complete example:
var $scope = angular.extend($rootScope.$new(true), data);
var template = angular.element($templateCache.get(templateName));
$compile(template)($scope);
return $timeout(function(){
return angular.element('<div/>').append(template).html();
});

Can't get element inside controller in Angular

I am trying to get an element inside an AngularJS controller like this:
var imageInput = document.getElementById("myImage");
It doesn't work, the element is null. However if I move the statement to a function that I bound with ng-click it works. Why? and how do I fix it?
Because it's not rendered for that time. If you write all code, we'll suggest better alternative than working with dom in controller (it's no good for you :) )
Like Karaxuna said, it has not been rendered yet.
Depending on what you are trying to do, you could use a directive to add behavior to your element : http://docs.angularjs.org/guide/directive.
Add some code so we can give you a better answer.
Not only it may have not been rendered in your dom, but also you should inject the $window in your controller and then access the document like this:
var myCtrl = function($scope,$window) {
Var document = $window.document
...
}
But you should seek a better way to manipulate your DOM instead of doing so in your controller (e.g. Writing a directive)

Categories

Resources