Can someone explain in simple terms?
The docs seems a bit obtuse. I am not getting the essence and the big picture of when to use one over the other. An example contrasting the two would be awesome.
Compiling - The modified source code is compiled into binary object code. This code is not yet executable. Linking - The object code is combined with required supporting code to make an executable program. This step typically involves adding in any libraries that are required.
Link: The link function deals with linking scope to the DOM. Using Code for Compile. While defining a custom directive we have the option to define a link against which either we can define a function or we have the option to assign an object which will have pre & post function.
The link option is just a shortcut to setting up a post-link function. controller: The directive controller can be passed to another directive linking/compiling phase. It can be injected into other directices as a mean to use in inter-directive communication.
Overview. Compiles an HTML string or DOM into a template and produces a template function, which can then be used to link scope and the template together. The compilation is a process of walking the DOM tree and matching DOM elements to directives.
compile function - use for template DOM manipulation (i.e., manipulation of tElement = template element), hence manipulations that apply to all DOM clones of the template associated with the directive.
link function - use for registering DOM listeners (i.e., $watch expressions on the instance scope) as well as instance DOM manipulation (i.e., manipulation of iElement = individual instance element).
It is executed after the template has been cloned. E.g., inside an <li ng-repeat...>, the link function is executed after the <li> template (tElement) has been cloned (into an iElement) for that particular <li> element.
A $watch() allows a directive to be notified of instance scope property changes (an instance scope is associated with each instance), which allows the directive to render an updated instance value to the DOM -- by copying content from the instance scope into the DOM.
Note that DOM transformations can be done in the compile function and/or the link function.
Most directives only need a link function, since most directives only deal with a specific DOM element instance (and its instance scope).
One way to help determine which to use: consider that the compile function does not receive a scope
argument. (I'm purposely ignoring the transclude linking function argument, which receives a transcluded scope -- this is rarely used.) So the compile function can't do anything you would want to do that requires an (instance) scope -- you can't $watch any model/instance scope properties, you can't manipulate the DOM using instance scope information, you can't call functions defined on the instance scope, etc.
However, the compile function (like the link function) does have access to the attributes. So if your DOM manipulations don't require the instance scope, you can use a compile function. Here's an example of a directive that only uses a compile function, for those reasons. It examines the attributes, but it doesn't need an instance scope to do its job.
Here's an example of a directive that also only uses a compile function. The directive only needs to transform the template DOM, so a compile function can be used.
Another way to help determine which to use: if you don't use the "element" parameter in the link function, then you probably don't need a link function.
Since most directives have a link function, I'm not going to provide any examples -- they should be very easy to find.
Note that if you need a compile function and a link function (or pre and post link functions), the compile function must return the link function(s) because the 'link' attribute is ignored if the 'compile' attribute is defined.
See also
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.
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.
For our purposes, I'm going to stress terminology, which is otherwise confusing:
The compiler SERVICE ($compile) is the angular mechanism that processes the DOM and runs the various bits of code in directives.
The compile FUNCTION is one bit of code within a directive, which is run at a particular time BY the compiler SERVICE ($compile).
Some notes about the compile FUNCTION:
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).
If you want to add other directives to (nested) elements, you either:
Have to add them during the compile phase.
Have to inject the compile service into the linking phase and compile the elements manually. BUT, beware of compiling something twice!
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 http://jsbin.com/imUPAMoV/1/edit. Basically, it just logs the steps to console.log.
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:
<tp>
<sp>
</sp>
</tp>
Angular compile SERVICE will call:
tp compile
sp compile
tp pre-link
sp pre-link
sp post-link
tp post-link
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.
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:
SCENARIO 1: Directive as a MACRO
You want to add a directive (say ng-show) dynamically to something in your template that you can derive from an attribute.
Say you have a templateUrl that points to:
<div><span><input type="text"></span><div>
and you want a custom directive:
<my-field model="state" name="address"></my-field>
that turns the DOM into this:
<div><span ng-show="state.visible.address"><input ng-model="state.fields.address" ...>
basically, you want to reduce boilerplate by having some consistent model structure that your directive can interpret. In other words: you want a macro.
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:
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() {}
};
}
The sequence of operations will be (you can see this via the jsbin mentioned earlier):
In the above example, no linking is needed, since all of the directive's work was done in compile FUNCTION.
At any point, the code in a directive can ask for the compiler SERVICE to run on additional elements.
This means that we can do exactly the same thing in a link function if you inject the compile service:
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);
}
});
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).
Thus, the compile phase is a much better choice for macro-style work.
SCENARIO 2: DOM configuration via scope data
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.
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.
Your model might push:
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'
};
and you might want a directive:
<form name="theForm">
<my-field model="state" metadata="metadata" name="address">
</form>
to auto-include the proper directives and divs to show the various validation errors:
<form name="theForm">
<div>
<input ng-model="state.address" type="text">
<div ng-show="theForm.address.$error.pattern">Address must begin with a number</input>
...
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).
In this case there is no point in writing a compile function. Link is really what you want. The steps would be:
Like so:
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
}
};
});
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.
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).
SCENARIO 3: two-way data binding via link
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With