Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>I beat my head against the wall on this for a couple of days, and I feel that a bit more explanation is in order.</p> <p>Basically, the docs mention that the separation is largely a performance enhancement. I would reiterate that the compile phase is mainly used when you need to modify the DOM BEFORE the sub-elements themselves are compiled.</p> <p>For our purposes, I'm going to stress terminology, which is otherwise confusing:</p> <p>The compiler SERVICE ($compile) is the angular mechanism that processes the DOM and runs the various bits of code in directives.</p> <p>The compile FUNCTION is one bit of code within a directive, which is run at a particular time BY the compiler SERVICE ($compile).</p> <p>Some notes about the compile FUNCTION:</p> <ol> <li><p>You cannot modify the ROOT element (the one your directive affects), since it is already being compiled from the outer level of DOM (the compile SERVICE has already scanned for directives on that element).</p></li> <li><p>If you want to add other directives to (nested) elements, you either:</p> <ol> <li><p>Have to add them during the compile phase.</p></li> <li><p>Have to inject the compile service into the linking phase and compile the elements manually. BUT, beware of compiling something twice!</p></li> </ol></li> </ol> <p>It is also helpful to see how the nesting and explicit calls to $compile work, so I've created a playground for viewing that at <a href="http://jsbin.com/imUPAMoV/1/edit" rel="noreferrer">http://jsbin.com/imUPAMoV/1/edit</a>. Basically, it just logs the steps to console.log.</p> <p>I'll state the results of what you'd see in that bin here. For a DOM of custom directives tp and sp nested as follows:</p> <pre><code>&lt;tp&gt; &lt;sp&gt; &lt;/sp&gt; &lt;/tp&gt; </code></pre> <p>Angular compile SERVICE will call:</p> <pre><code>tp compile sp compile tp pre-link sp pre-link sp post-link tp post-link </code></pre> <p>The jsbin code also has the tp post-link FUNCTION explicitly call the compile SERVICE on a third directive (up), which does all three steps at the end.</p> <p>Now, I want to walk through a couple of scenarios to show how one might go about using the compile and link to do various things:</p> <p>SCENARIO 1: Directive as a MACRO</p> <p>You want to add a directive (say ng-show) dynamically to something in your template that you can derive from an attribute.</p> <p>Say you have a templateUrl that points to:</p> <pre><code>&lt;div&gt;&lt;span&gt;&lt;input type="text"&gt;&lt;/span&gt;&lt;div&gt; </code></pre> <p>and you want a custom directive:</p> <pre><code>&lt;my-field model="state" name="address"&gt;&lt;/my-field&gt; </code></pre> <p>that turns the DOM into this:</p> <pre><code>&lt;div&gt;&lt;span ng-show="state.visible.address"&gt;&lt;input ng-model="state.fields.address" ...&gt; </code></pre> <p>basically, you want to reduce boilerplate by having some consistent model structure that your directive can interpret. In other words: you want a macro. </p> <p>This is a great use for the compile phase, since you can base all of the DOM manipulations on things you know just from the attributes. Simply use jQuery to add the attributes:</p> <pre><code>compile: function(tele, tattr) { var span = jQuery(tele).find('span').first(); span.attr('ng-show', tattr.model + ".visible." + tattr.name); ... return { pre: function() { }, post: function() {} }; } </code></pre> <p>The sequence of operations will be (you can see this via the jsbin mentioned earlier):</p> <ol> <li>The compile SERVICE finds my-field</li> <li>It calls the compile FUNCTION on the directive, which updates the DOM.</li> <li>The compile SERVICE then walks into the resulting DOM, and COMPILES (recursively)</li> <li>The compile SERVICE then calls pre-link top-down</li> <li>The compile SERVICE then calls post-link BOTTOM UP, so my-field's link function is called AFTER interior nodes have been linked.</li> </ol> <p>In the above example, no linking is needed, since all of the directive's work was done in compile FUNCTION.</p> <p>At any point, the code in a directive can ask for the compiler SERVICE to run on additional elements.</p> <p>This means that we can do exactly the same thing in a link function if you inject the compile service:</p> <pre><code>directive('d', function($compile) { return { // REMEMBER, link is called AFTER nested elements have been compiled and linked! link: function(scope, iele, iattr) { var span = jQuery(iele).find('span').first(); span.attr('ng-show', iattr.model + ".visible." + iattr.name); // CAREFUL! If span had directives on it before // you will cause them to be processed again: $compile(span)(scope); } }); </code></pre> <p>If you're sure that the elements you are passing to $compile SERVICE originally were directive-free (e.g. they came from a template you defined, or you just created them with angular.element()), then the end result is pretty much the same as before (though you may be repeating some work). However, if the element had other directives on it, you just caused those to be processed again, which can cause all sorts of erratic behavior (e.g. double-registration of events and watches).</p> <p>Thus, the compile phase is a much better choice for macro-style work.</p> <p>SCENARIO 2: DOM configuration via scope data</p> <p>This one follows from the example above. Suppose you need access to the scope while manipulating the DOM. Well, in that case, the compile section is useless to you, since it happens before a scope is available. </p> <p>So, let's say you want to pimp out an input with validations, but you want to export your validations from a server-side ORM class (DRY), and have them auto-apply and generate the proper client-side UI for those validations.</p> <p>Your model might push:</p> <pre><code>scope.metadata = { validations: { address: [ { pattern: '^[0-9]', message: "Address must begin with a number" }, { maxlength: 100, message: "Address too long" } ] } }; scope.state = { address: '123 Fern Dr' }; </code></pre> <p>and you might want a directive:</p> <pre><code>&lt;form name="theForm"&gt; &lt;my-field model="state" metadata="metadata" name="address"&gt; &lt;/form&gt; </code></pre> <p>to auto-include the proper directives and divs to show the various validation errors:</p> <pre><code>&lt;form name="theForm"&gt; &lt;div&gt; &lt;input ng-model="state.address" type="text"&gt; &lt;div ng-show="theForm.address.$error.pattern"&gt;Address must begin with a number&lt;/input&gt; ... </code></pre> <p>In this case you definitely need access to the scope (since that is where your validations are stored), and are going to have to compile the additions manually, again being careful not to double-compile things. (as a side note, you would need to set a name on the containing form tag (I'm assuming theForm here), and could access it in link with iElement.parent().controller('form').$name).</p> <p>In this case there is no point in writing a compile function. Link is really what you want. The steps would be:</p> <ol> <li>Define a template that is completely devoid of angular directives.</li> <li>Define a link function that adds the various attributes</li> <li>REMOVE any angular directives that you might allow on your top-level element (the my-field directive). They have already been processed and this is a way to keep them from being double-processed.</li> <li>Finish by calling the compile SERVICE on your top-level element</li> </ol> <p>Like so:</p> <pre><code>angular.module('app', []). directive('my-field', function($compile) { return { link: function(scope, iele, iattr) { // jquery additions via attr() // remove ng attr from top-level iele (to avoid duplicate processing) $compile(iele)(scope); // will pick up additions } }; }); </code></pre> <p>You could, of course, compile the nested elements one-by-one to avoid having to worry about the duplicate processing of ng directives when you compile the top-level element again.</p> <p>One final note on this scenario: I implied you'd be pushing the definition of the validations from a server, and in my example I've shown them as data already in the scope. I leave it as an exercise for the reader to figure out how one might deal with needing to pull that data from a REST API (hint: deferred compile).</p> <p>SCENARIO 3: two-way data binding via link</p> <p>Of course the most common use of link is to simply hook up the two-way data binding via watch/apply. Most directives fall into this category, so it is adequately covered elsewhere.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload