AngularJS: manual $compile vs natural $compile over a recursive directive - angularjs

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/

Related

angularjs compile and link dom recursive manipulation

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.

Call function after angular all directives are loaded

I have multiple custom directives in my ngApp. The demo code is:
<top-nav></top-nav>
<left-sidebar></left-sidebar>
<div class="content">
....
</div>
The height of the 'left-sidebar' needs to be adjusted according to the height of the 'top-nav'.
Similarly there are other interdependent UI tasks. I want to run the post-load code (say app.initializeUI();) only after ALL the directives have been loaded and rendered.
Now, How do I achieve that?
EDIT :
Currently I am using the following code :
App.run(function($timeout){
$timeout(function(){ app.init() },0);
});
This runs fine, however, I am not sure this is the perfect way of doing this.
EDIT 2:
For people who want to avoid setting styles in js - use CSS Flexbox. I find it much better than calculating heights post page load. I got a good understanding of flexbox here
I would create an attribute directive with isolated scope, set terminal=true and add it to your body tag. Then in your link function, setup a $watch - isInitialized which is initially false. In your $watch handler, call your app.init(), and then de-register the $watch, so that it is always initialized once.
Setting up a terminal directive has consequences - no other directive can run after the terminal directive on the same element. Make sure this is what you want. An alternative is to give your directive the lowest possible value so that it is compiled first, and linked last.
The important pieces to this solution are:
By adding your Directive to the body tag, you ensure that it is linked last.
By adding a $watch, you ensure that all other directives have gone through a digest cycle, so by the time your $watch handler is called, all other directives should have already rendered.
Note of caution: The digest cycle may run several times before scope changes stabalise. The above solution only runs after the first cycle, which may not be what you want if you really want the final rendering.

Why does less directives/more html take longer to compile than more directives/less html?

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

Dynamically creating an ng-switch inside a directive

I'm trying to create a new directive that selects among several children using an ng-switch. This example is not much more than creating an ng-switch inside a directive, but eventually the directive will have more display sugar and some automatic functions so it seemed that creating a directive was the right solution.
My progress so far is in this plunker:
http://plnkr.co/edit/yeCiIOCQswYJHyTozQUZ
The $compile I'm doing seems to be evaluating the switch, and determining that the value doesn't match any of the when clauses which shouldn't be true. You can see that by inspecting the elements in the rendered picker.
I'm also concerned that calling $compile at this stage seems to have thrown away the item list, so it seems like I'm barking up the wrong tree.
How do I get the transcluded content to re-evaluate within the current state?
Update
I think I was barking up the wrong tree. Mathew's answer got me started in the right direction, so it was a big help.
As far as I can tell trying to construct a directive (ng-switch) inside a directive is a bad idea. In the previous plunker when the compile happened the template was changed permanently. That means if I changed the which parameter it wouldn't update. That's what was smelling funny to me in the first place.
Here is a revised plunker:
http://plnkr.co/edit/WUVgdXjwedxO4356321s
In this case, there's a watch on the 'which' value that refires the transclusion. That function removes the previous entry (if any) and adds the new one. There's a couple added benefits.
First I removed the 'item' directive. There's no reason for it to exist, since I'm just looking at the class. Second, I used $animator to do the list manipulation. That means you can add ng-animate to the picker and get animation effects.
I hope that helps someone else looking at this question.
There were two problems with your code:
1) Your template had which being evaluated so your on was becoming the number 1, instead of the variable you wanted which is "which":
template: '<div><div ng-switch on="which"></div></div>',
2) When you used compile, you needed to pass in the $scope like so:
$compile(sel)($scope);
Here's an updated plnkr for you: http://plnkr.co/edit/Q6ViJBvkLwQRgUKYMfS9?p=preview

angularjs : logging scope property in directive link function displays undefined

I have this basic plnkr which just implements a basic "Hello, X" directive.
In the link function I am logging scope.name but I get undefined? Why is it so? Shouldn't it log the value of name property in console?
This is a known "problem" where interpolation of # attributes happens after linking function is invoked. There is a pull request open to change this issue but it is not clear if this one is going to be merged.
In the meantime a way of getting an interpolated value is by observing an attribute like so:
attrs.$observe('hello', function(changedValue){
console.log(scope.name);
});
And the plunk: http://plnkr.co/edit/Lnw6LuadTLhhcOTsPC8w?p=preview
So, at the end of the day this is a bit confusing behavior of AngularJS that might be changed in the future.
Pawel is right (https://stackoverflow.com/a/14552200/287070) but I wanted to add that the problem is that any attribute that contains {{}} interpolation will be set to null in the attrs parameter during the link function as the first $digest since the compilation has not yet run to evaluate these.
The fact that # bindings are null in linking functions is just a symptom of this.
Currently there is no real fix, since we can't start running $digests in the middle of the compilation process. So $observe (or $watch) is the only real way to get hold of these values.
For those in 2015 who are reading this post, please note that the way Angular handles "#" attributes has changed.
Angular 1.2 onwards, interpolation occurs prior to the invocation of the linking function.
An excellent post on this topic is present here.

Resources