Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with:

Directive Controller And Link Timing In AngularJS

By Ben Nadel on

I've talked about the timing of directives in AngularJS a few times before. But, it's a rather complicated topic, so I don't mind digging a bit deeper. This time, I'm looking at the timing of directive controllers vs. directive link functions. As the DOM (Document Object Model) is compiled by AngularJS, the directive controllers and link functions execute at different parts of the compile lifecycle.


 
 
 

 
  
 
 
 

Run this demo in my JavaScript Demos project on GitHub.

When AngularJS compiles the DOM, it walks the DOM tree in a depth-first, top-down manner. As it walks down the DOM, it instantiates the directive controllers. Then, when it gets to the bottom of a local DOM tree branch, it starts linking the directives in a bottom-up manner as it walks back up the branch. This doesn't mean that all directive controllers are run before all directive linking; it simply means that in a local DOM branch, the directive controllers are instantiated before they are linked.

To see this in action, I've put together a very simple DOM tree in which each element has a unique (but almost identical) directive. As each directive controller and link function is executed, it will log to the console. This way, we can see the timing of the various methods in relation to the DOM tree structure.

  • <!doctype html>
  • <html ng-app="Demo">
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • Directive Controller And Link Timing In AngularJS
  • </title>
  • </head>
  • <body>
  •  
  • <h1>
  • Directive Controller And Link Timing In AngularJS
  • </h1>
  •  
  • <div bn-outer>
  •  
  • <p bn-mid>
  •  
  • <span bn-inner>
  •  
  • Woot!
  •  
  • </span>
  •  
  • </p>
  •  
  • <p bn-second-mid>
  •  
  • Woot, indeed!
  •  
  • </p>
  •  
  • </div>
  •  
  •  
  • <!-- Load scripts. -->
  • <script type="text/javascript" src="../../vendor/jquery/jquery-2.0.3.min.js"></script>
  • <script type="text/javascript" src="../../vendor/angularjs/angular-1.2.4.min.js"></script>
  • <script type="text/javascript">
  •  
  • // Create an application module for our demo.
  • var app = angular.module( "Demo", [] );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // I demonstrate the timing of directive execution.
  • app.directive(
  • "bnOuter",
  • function() {
  •  
  • function Controller( $scope ) {
  •  
  • console.log( "Outer - Controller" );
  •  
  • }
  •  
  •  
  • function link( $scope, element, attributes, controller ) {
  •  
  • console.log( "Outer - Link" );
  •  
  • }
  •  
  •  
  • // Return directive configuration.
  • return({
  • controller: Controller,
  • link: link
  • });
  •  
  • }
  • );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // I demonstrate the timing of directive execution.
  • app.directive(
  • "bnMid",
  • function() {
  •  
  • function Controller( $scope ) {
  •  
  • console.log( "Mid - Controller" );
  •  
  • }
  •  
  •  
  • function link( $scope, element, attributes, controller ) {
  •  
  • console.log( "Mid - Link" );
  •  
  • }
  •  
  •  
  • // Return directive configuration.
  • return({
  • controller: Controller,
  • link: link
  • });
  •  
  • }
  • );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // I demonstrate the timing of directive execution.
  • app.directive(
  • "bnSecondMid",
  • function() {
  •  
  • function Controller( $scope ) {
  •  
  • console.log( "Second Mid - Controller" );
  •  
  • }
  •  
  •  
  • function link( $scope, element, attributes, controller ) {
  •  
  • console.log( "Second Mid - Link" );
  •  
  • }
  •  
  •  
  • // Return directive configuration.
  • return({
  • controller: Controller,
  • link: link
  • });
  •  
  • }
  • );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // I demonstrate the timing of directive execution.
  • app.directive(
  • "bnInner",
  • function() {
  •  
  • function Controller( $scope ) {
  •  
  • console.log( "Inner - Controller" );
  •  
  • }
  •  
  •  
  • function link( $scope, element, attributes, controller ) {
  •  
  • console.log( "Inner - Link" );
  •  
  • }
  •  
  •  
  • // Return directive configuration.
  • return({
  • controller: Controller,
  • link: link
  • });
  •  
  • }
  • );
  •  
  • </script>
  •  
  • </body>
  • </html>

Before we look at the console output, take note that there are two "mid" branches. This means that AngularJS has two branches to explore before it can fully walk back up to the top DOM node. That said, when we do run the above code, we get the following console log output:

Outer - Controller
Mid - Controller
Inner - Controller
Inner - Link
Mid - Link
Second Mid - Controller
Second Mid - Link
Outer - Link

As you can see, as AngularJS walks the DOM tree, it instantiates the directive controllers on the way down; then, it links the directives on the way back up.

This is an important difference. While you can only access the DOM tree in the bottom-up linking phase, the directive controller can provide a hook into the top-down lifecycle. This can be critical if you have to handle DOM events based on when elements of the DOM tree came into existence. The linking phase can never give you that because it's executed in reverse.




Reader Comments

This is great as usual Ben, code exploration is a blast and I'm glad you manage to find the time to do it; gives me something to look forward to reading. I'd like to mention that there are actually two linking phases, pre and post. The part you've hooked into here is the post-linking phase (ng's default) thus the bottom-up logging. This makes sense as the default because all the child elements would have been linked by the time the function is called, making it *safe to do any DOM transformations you might require. You can hook into the pre-linking phase quite easily by changing ```link: link``` to ```link: { pre: link }``` and results in an output of:

Outer - Controller
Outer - Link
Mid - Controller
Mid - Link
Inner - Controller
Inner - Link
Second Mid - Controller
Second Mid - Link

It is worth noting that you can specify both the pre and post-linking functions together: ```link: { pre: preLink, post: postLink }```. Imagine the example having specified pre/post linking functions that logged their order respectively and you'd get this:

Outer - Controller
Outer - Pre Link
Mid - Controller
Mid - Pre Link
Inner - Controller
Inner - Pre Link
Inner - Post Link
Mid - Post Link
Second Mid - Controller
Second Mid - Pre Link
Second Mid - Post Link
Outer - Post Link

This gives us a couple ways to hook into the top-down life cycle: the directive controller **and** the pre-linking function. Pretty cool stuff.

Reply to this Comment

@Kevin,

Your Mardown-fu is not lacking... my comments don't support Markdown, yet! That's on my queue of updates to make, I just haven't carved out the time to make it happen yet.

Thanks for clarifying the pre/post linking. To be honest, I've seen that in the documentation a hundred times and I never really understood the difference. Or, even if I understood the difference, I am not sure I understood the implications of doing it one way or the other. As such, I never messed with it. But, it sounds like it's just a timing issue, and nothing too much more complex than that (though timing does, of course, have implications in regard to DOM availability).

Awesome stuff!

Reply to this Comment

This is amazing stuff to know. Explained with a great example. I was asked similar question in Interview today and I couldn't explain properly. This gives me more clarity.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.