What is the difference between compile and link function in angularjs

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.

  • 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:

  1. 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).

  2. If you want to add other directives to (nested) elements, you either:

    1. Have to add them during the compile phase.

    2. 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):

  1. The compile SERVICE finds my-field
  2. It calls the compile FUNCTION on the directive, which updates the DOM.
  3. The compile SERVICE then walks into the resulting DOM, and COMPILES (recursively)
  4. The compile SERVICE then calls pre-link top-down
  5. The compile SERVICE then calls post-link BOTTOM UP, so my-field's link function is called AFTER interior nodes have been linked.

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:

  1. Define a template that is completely devoid of angular directives.
  2. Define a link function that adds the various attributes
  3. 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.
  4. Finish by calling the compile SERVICE on your top-level element

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.

From the docs:

Compiler

Compiler is an angular service which traverses the DOM looking for attributes. The compilation process happens into two phases.

  1. Compile: traverse the DOM and collect all of the directives. The result is a linking function.

  2. Link: combine the directives with a scope and produce a live view. Any changes in the scope model are reflected in the view, and any user interactions with the view are reflected in the scope model. Making the scope model a single source of truth.

Some directives such ng-repeat clone DOM elements once for each item in collection. Having a compile and link phase improves performance since the cloned template only needs to be compiled once, and then linked once for each clone instance.

So at least in some cases, the two phases exist separately as an optimization.


From @UmurKontacı:

If you are going to make DOM transformations, it should be compile. If you want to add some features that are behavior changes, it should be in link.

This is from Misko 's talk on directives. http://youtu.be/WqmeI5fZcho?t=16m23s

Think of the compiler function as the thing that works on a template and the thing that is allowed to change the template itself by, for example, adding a class to it or anything like that. But it's the linking function that actually does the work of binding the two together because the linking function has access to the scope and it's the linking function that executes once for each instantiation of the particular template. So the only kind of things you can placed inside of the compile functions are things that are common across all of the instances.

Two Phases: Compile and Link

Compile:

Traverse the DOM tree looking for directives (elements / attributes / classes / comments). Each compilation of a directive may modify its template, or modify its contents which has not been compiled yet. Once a directive is matched, it returns a linking function, which is used in a later phase to link elements together. At the end of the compile phase, we have a list of compiled directives and their corresponding linking functions.

Link:

When an element is linked, the DOM tree is broken at its branch point in the DOM tree, and the contents are replaced by the compiled (and linked) instance of the template. The original displaced content is either discarded, or in the case of transclusion, re-linked back into the template. With transclusion, the two pieces are linked back together (kind of like a chain, with the template piece being in the middle). When the link function is called, the template has already been bound to a scope, and added as a child of the element. The link function is your opportunity to manipulate the DOM further and setup change listeners.