Can someone tell me. Does calling $compile cause angular to traverse the entire DOM? I read somewhere that compiling is an expensive task as it requires angular to traverse the DOM looking for directives, so calling it many times can slow things down. But I can't find where I read it now.
I would like to know if I did the following would it just compile the markup or
would it also force angular to scan the entire DOM?
$compile(<div>{{name}}</div>)(scope);
Calling the $compile service only evaluates the nodes that you pass to it. You can see this in the source code: https://github.com/angular/angular.js/blob/master/src/ng/compile.js#L810. It does not need to evaluate the entire DOM to compile the nodes that you have passed to it.
Related
I read document from AngualrJS : https://docs.angularjs.org/guide/component
And when I am reading to "Example of a component tree" section, I got confused about how a component tree's loading works, because there is nowhere to find the loading order.
How angular find the root directive and nested directive (may be in a template)? and Which component should start work first? I mean, If a nested component() is called earlier than its directive's appearance, will it be called later when its directive appears? How angular knows the appropriate component to call when a directive/template load? Or just iterate all components method? Or load all components first, then according index.html to form a component hierarchy, then call it properly?
Is there anybody kindly explain? thanks a lot!
Thanks estus!, your anwser is really helpful! According to AngularJS : How does the HTML compiler arrange the order for compiling? and Pete Bacon Darwin's example :
Just in my opinion:
Angular call all components comiple() method first to complete whole template loading, then call their controller() methods regard to directives hierarchy.
And this comes from : How directives are compiled
It's important to note that Angular operates on DOM nodes rather than
strings. Usually, you don't notice this restriction because when a
page loads, the web browser parses HTML into the DOM automatically.
HTML compilation happens in three phases:
$compile traverses the DOM and matches directives.
If the compiler finds that an element matches a directive, then the directive is added to the list of directives that match the DOM
element. A single element may match multiple directives.
Once all directives matching a DOM element have been identified, the compiler sorts the directives by their priority. Each
directive's compile functions are executed. Each compile function
has a chance to modify the DOM. Each compile function returns a
link function. These functions are composed into a "combined" link
function, which invokes each directive's returned link function.
$compile links the template with the scope by calling the combined linking function from the previous step. This in turn will
call the linking function of the individual directives, registering
listeners on the elements and setting up $watchs with the
scope as each directive is configured to do.
I've been working with angular for a month or so now. I'm having an issue working with dom manipulation. As an exercise, I want to create a directive that would repeat a nested object to a tree view. Please don't send me links of online example. I found plenty but most don't explain what they are doing or more importantly why the code is written that way.
I have create a plnkr as a sample code to test with located here: http://plnkr.co/edit/Zcx63dJZxQyDsjAHIALh?p=preview
Every example I found online explains that all the compile functions run before the pre or post link and the post link is where events should be registered and bound. I can also either use a link or a compile function in my directive and a compile function returns a post link. You can see in the plnkr that everything is setup this way.
I understand that the link function can take a transclude function that creates a clone of the element. This is where I'm stuck. I tried to append the clone to the element but i'm always getting my browser to freeze and being none responsive.
In the angular documentation it says:
Note: The compile function cannot handle directives that recursively use themselves in their own templates or compile functions. Compiling these directives results in an infinite loop and a stack overflow errors. This can be avoided by manually using $compile in the postLink function to imperatively compile a directive's template instead of relying on automatic template compilation via template or templateUrl declaration or manual compilation inside the compile function
but there's no example on what's the process of manipulating the dom. How do we handle recursion in the link function using compile? In some examples they clear the element html and then append the clone. Why? How can I recursively build and append templates? Is it better to have the element transcluded into a template or is it better to have my template in the directive and then clone it?
I hope you can help me as I can't find anything that would go in details of the compile function and the steps that are needed when it comes to recursive dom manipulation. If you want to help please provide an explanation of each line of the code.
Thank you for the help,
Hi I fixed the plunkr: http://plnkr.co/edit/gbeljxdShUazBJux7hXB
The biggest problem I saw was that your recursion never ended which is why there was a stack overflow.
let me know if you have any questions.
Hope this helps.
I've tried to create my own recursive directive with AngularJS, wich calls itself to transform an object in a view with a pretty JSON format. Well, first I used a ng-include calling a script with the template, with a ng-if inside of it verifing if the current value was an object, if is, the template call itself.
I always think this is a ungly way to doind that, so I transform in a directive, much more simple.
More and less... Because I discover the world of recursive directives and found a lot of things, and most interesting. I even gave it a read in source code of Angular in github (I recommend you to read: https://github.com/angular/angular.js), with was a good things.
I searched so hard, and I thinkg I'm almost finding the awser that will cherish my heart! Because I learn a lot of new things and you guys, will help me.
Look my code in the link below: https://github.com/Daymannovaes/htmljs/blob/master/js/directives/recursiveDataTemplateDirective.js
my directive are: recursive-data-template="data", where data are an object. This directive will loop over the key and values of this object, and if the value was an object, will do this again. The condition are made with ng-if="isObject(value)".
Ok, my first problem was the infinite loop. I need to remove the content in the compile phase and then imperatively compile the content in the postLink phase. My question:
** Why the manual compile do not falls on the same problem of the infinite loop? **
I'm compiling the same content, no condition are made (if the if(!compiledContent) was removed, the infinite loop still not occurring), the diference (I think) are only they are maded in diferent place, but I wasn't able to find in internet someone who awser my question!
So, thank you!
(if the link doesn't working, here are the important code):
compile: function(templateElement, templateAttributes) {
/*
in compile time, we need to remove the innerHTML of template(url) because of its recursive.
Because of its recusiveness, when the $compile start to read the DOM tree and find a
recursiveDataTemplate directive (even its not will be evaluated all time by link function
because the ng-if, whatever) its start the do all the process again. So, we need the .remove()
*/
var templateDirectiveContent = templateElement.contents().remove();
var compiledContent;
return function($scope, linkElement, linkAttributes) {
/*
This verification avoid to compile the content to all siblings, because
when you compile the siblings, don't work (I don't know why, yet).
So, doing this we get only the top level link function (from each iteration)
*/
if(!compiledContent) {
compiledContent = $compile(templateDirectiveContent);
}
/*
Calling the link function passing the actual scope we get a clone object
wich contains the finish viewed object, the view itself, the DOM!!
Then, we attach the new dom in the element wich contains the directive
*/
compiledContent($scope, function(clone) {
linkElement.append(clone);
});
};
},
}
<ul>
<li data-ng-repeat="(key, value) in fields">
<span data-ng-if="!isNumber(key)">
{{key}}:
</span>
<span data-ng-if="isObject(value)" recursive-data-template="value"></span>
<span data-ng-if="!isObject(value)">
{{value}}
</span>
</li>
</ul>
I believe this excerpt from the official documentation is relevant to what you're asking:
Note: The compile function cannot handle directives that recursively use themselves in their own templates or compile functions. Compiling these directives results in an infinite loop and a stack overflow errors. This can be avoided by manually using $compile in the postLink function to imperatively compile a directive's template instead of relying on automatic template compilation via template or templateUrl declaration or manual compilation inside the compile function.
And going from the code you've provided, you've seem to have done what this note is suggesting - that is, manually compiling inside the function (postLink) you're returning for the compile property of your directive.
About the reason why compiling during the postLink rather than the compile phase avoids the infinite recursion, that's because all the elements of the DOM are compiled wether they are actually used or not, while the link will only be triggered when the element is actually linked: for insteance if a higher ng-if is falsy, its children element will not be preLinked, and thus neithr postLinked... At least from my understanding!
I recommend this good article: http://www.jvandemo.com/the-nitty-gritty-of-compile-and-link-functions-inside-angularjs-directives/
In our app, we have several layers of nested directives. In an attempt to speed up the digest cycle, we removed some top level directives that have data-bindings to static data. Instead, we just generated the html from the static data, and compiled that. However, after doing that, we started getting unresponsive script warnings in Firefox. The compile time on this generated html was taking too long.
I'm hoping to gain a better understanding of the compile process in these 2 different scenarios so that we can optimize correctly in the future. Here is a very simplified fiddle that shows the problem. Here is the important part of it:
var repeaterHtml = '<div repeater="allData"></div>';
var staticHtml = '';
angular.forEach($scope.allData, function(currData, currIndex) {
staticHtml += '<div dir1="allData[' + currIndex + ']"></div>';
});
$element.append(staticHtml);
When using the staticHtml it takes significantly longer to compile than when just using the repeater directive. Why is this? Is it purely because there is more html to go through? As angular compiles the repeater directive does it not have to do the same compiling as I was doing manually? When the final DOM is exactly the same, what is it about the repeater directive that makes it compile so much faster?
I'd appreciate any insight into this. Thanks.
EDIT - your repeater directive does nothing special - it is just a wrapper for ng-repeat. To understand why repeater is faster than manipulating the DOM from the controller, you need to understand how ng-repeat works, what are compilation and linking phases. AFAIR Miško Hevery - has a talk about this here http://www.youtube.com/watch?v=WqmeI5fZcho
There are few things you are doing in your example that cause it to be slower than angular ng-repeat
Manipulating DOM in controller - don't do that! This practice is very bad! The reason ng-repeat is faster in this case is because it compiles the DOM once and only produces copies of the repeated element for each element in the array. In your case, you are creating element from string after the DOM has been rendered, then you modify the DOM structure by appending the element to the document, and after that, you are compiling the element you have just created against scope, which again modifies the document's DOM! You only need to compile the element once, and the append it to the parent (but do it in a directive - not in controller).
If you want to have a faster implementation of ng-repeat (but with less features - like auto updating) then write a directive that would do almost the same thing but using single template element and modify the document's DOM only once.
If you want to have better understanding how this works, I suggest reading ngRepeatDirective https://github.com/angular/angular.js/blob/master/src/ng/directive/ngRepeat.js
I'm using the router (was using the built in one, now using ui-route but solutions for either are fine) in Angular.JS to switch between control/template pairs. When switching back and forth between a couple of these pages it takes up to a second to setup the DOM each time which looks terrible. Is there anyway of having angular keep around the DOM tree instead of recreating it each time. I guess I'd like to just hide/sow the bits for each page rather than remove/re-create them each time.
Any suggestions welcome!
You have to write your own ng-view directive to create such functionality.
The basic idea behind it is:
Before the route changes, instead of destroying the current view element, and scope, you just put it in an invisible cache DIV, and deregister scope listeners. Give the element a data attribute with the $$route.templateUrl to be able to get it back.
Then you fetch the next view from the server.
Before the route changes you check for cache item existance, and if it is in your cache div get the element from the cache, re-register listeners and put the current view the cache.
The tricky part is not to mess the $scopes up. So you might need a constructor and destructor in your $scope for the event, and maybe for the $watchers as well. I'm not sure.
But to be honest, if you using the template cache and still takes 1 second or so to render, then you might have some inefficient $watch expression, or a huge ng-repeat. You should consider some refact.
I've been researching this myself. I am working on rather old hardware in an unaccelerated browser. Initial render is a problem. My early work-around was to cache pre-compiled templates, as you are attempting. I found that this provided only a minimal speed improvement.
The real bottleneck was coming from my ng-repeat directives, the number of reflows that resulted and the number of DOM nodes/watchers I was building in each iteration.
Some sources have suggested creating a custom directive which manually assembles the dom and appends it in one go. The result is very minimal reflow and no watchers. The downside is very large. No more angular fun and a lot of unnecessary work. More importantly, none of this provided a large enough speed improvement to justify the work.
I ultimately found that the best speed improvement came from forcing hardware acceleration for each ng-repeat iteration. As suggested above, the ng-animate directive in newer versions angular make this relatively trivial.
You will see immediate page render, with minor reflow hiccups. ng-cloak does not help here. Due to the animation request, the page is not suposed to be cloaked while the repeat renders. These can, however, be rendered reasonably well with a bit of clever fun. I'm actually hiding the ng-repeat until the $location changes, showing a progress indicator in the meantime, and then toggling my ng-show. This works really nicely.
Having said all of that, precompiling your templates should be done as follows.
1) When your app starts, create a new cache for yourself. See http://docs-angularjs-org-dev.appspot.com/api/ng.$cacheFactory
2) Populate this cache with compiled templates. Inject $compile and call it on each template. Compile returns a function which you will later call against your scope. Key this function in your cache as you see fit.
3) Create a custom directive which accepts a cache key as an attribute. Inside this directive, query your compile cache for the correct compile function. Call the function against your current scope, and append the resulting DOM to the element passed into the directive.
4) Sorta win :).
If you move up to Angular 1.1.5, you can use the ng-animate attribute on you ng-view tag.
I'm not 100% sure, but I think it does some DOM caching to make the transitions work better. You could try adding adding ng-animate attribute to your tag. That might take care of it for you.