angularjs compile and link dom recursive manipulation - angularjs

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.

Related

Angularjs : How can i know when to use "Compile" when to use "Link" in directives?

I am very new to angularjs. I have little bit confusion with link and compile usage in directives. Can anyone please tell me in which scenarios we have to use link and compile.
What is the difference between compile and link function in angularjs
Already answered on stack overflow and has an excellent explanation
The compile phase
When the DOM is loaded Angular starts the compile phase, where it traverses the markup top-down, and calls compile on all directives. Graphically, we could express it like so:
An image illustrating the compilation loop for children
It is perhaps important to mention that at this stage, the templates the compile function gets are the source templates (not instance template).
The link phase
DOM instances are often simply the result of a source template being rendered to the DOM, but they may be created by ng-repeat, or introduced on the fly.
Whenever a new instance of an element with a directive is rendered to the DOM, the link phase starts.
In this phase, Angular calls controller, pre-link, iterates children, and call post-link on all directives, like so:
Below links will give you clear idea for compile vs link.
Angular directives - when and how to use compile, controller, pre-link and post-link
http://odetocode.com/blogs/scott/archive/2014/05/28/compile-pre-and-post-linking-in-angularjs.aspx

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

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/

Controller <directiveName>, Required by Directive 'ngClass', Can't be Found

I get the error "Controller 'gssResponseGroup', required by directive 'ngClass', can't be found!" when using the linked Plunker files. Problem is, sometimes it works perfectly fine and other times I get this error. My guess is that the order of loading/compiling of the directives is not consistent.
Anyone have any ideas?
Plunker
I don't see why it states that it can't be found. It is defined right above in the same JavaScript file.
I can't recreate your error, but I am guessing the imperative transclude() call in your link function is creating a race condition. You're using pre-1.2 transcludes with 1.3, so take a look at the current docs for your transcluding needs: https://docs.angularjs.org/guide/directive
I'm also pretty sure the error is not referring to a dependency injection lookup failure, but because the require: '^gssResponseGroup', line in your sub-directive can't find the not-yet-instantiated / linked controller of a parent directive, since it's being transcluded in an ad-hoc way.

Does Angular's $compile method scan the entire DOM?

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.

How to init JS libraries within directive's controller

I'm writing a directive to wrap plupload functionality.
The directive is intended to be used as you can see here: http://pastebin.com/sddR0UL7
Here (http://pastebin.com/c09LWeu4) you can find the template the directive reference.
And here's the directive's code (coffeescript): http://pastebin.com/SCwbkHWf
When visiting the page containing the directive I can see "Error: p is null" which
signifies that plupload could not be initialized (usually because references to
container is not defined).
Executing directive step by step I can see that the attributes it references are all
defined, so I think that the error is due to DOM not being compiled/linked yet.
How can I overcome this problem?
Thanks in advance for your help
I think I've found the culprit.
It seems like plupload.init manipulates the DOM so initializing the component within
controller breaks the "don't manipulate DOM in controller" rule.
So, by movine the initialization of plupload into "link" function, everything works
as advertised.
Still I'm open for other advices or best practices.
Thanks

Resources