Looking At Attribute Interpolation Workflow Changes In AngularJS

When you create a directive in AngularJS, you can consume the attributes associated with the current element. And, if a given attribute uses interpolation, AngularJS provides the $observe() method for monitoring the dynamic, interpolated value over time. But, in this post, I want to look at the initial value of the interpolated attribute; and, how that initial value has changed in the recent releases of AngularJS.

Run this demo in my JavaScript Demos project on GitHub.

Attribute interpolation is, itself, implemented using a directive. This means that, ultimately, understanding the attribute interpolation workflow boils down to understanding how directives get compiled and linked. This is a somewhat complicated process of which I only have a fuzzy grasp. But, my high-level understanding is that the compilation and linking process takes place over two phases:

Pre-linking - AngularJS is walking down the DOM (Document Object Model) tree, executing compile and pre-link functions in priority order.

Post-linking - AngularJS is walking back up the DOM tree, executing post-link functions in reverse priority order.

NOTE: I believe the logic outlined above has actually changed over time, as well, so it's a little hard to reconcile the documentation with the behavior that I see in my tests. So, please take the explanation portion of this post with a grain of salt. For example, the reverse-priority logic used in post-linking is new in AngularJS 1.2.

In earlier versions of AngularJS (pre 1.2), attribute interpolation had several distinct characteristics:

It had priority 100.

It was configured using a single link function (ie, post-linking phase).

It set the initial value of the attribute to "undefined".

It didn't set the first interpolated value until it was inside the $observe() handler.

The last point is perhaps the most important take-away for earlier versions of AngularJS. Since the first interpolated value wasn't defined until inside the first invocation of the $observe() method callback, it means that the first interpolated value was only available asynchronously. Or, in other terms, it was not available inside the link() function of any other relevant directive.

To see this in action, here's a simple demo that inspects the interpolated value of an attribute in AngularJS 1.0.8:

<!doctype html> <html ng-app="Demo"> <head> <meta charset="utf-8" /> <title> Looking At Attribute Interpolation Workflow In AngularJS 1.0.8 </title> </head> <body ng-controller="AppController"> <h1> Looking At Attribute Interpolation Workflow In AngularJS 1.0.8 </h1> <!-- Here, wer are using a Directive that uses attribute interpolation. --> <p bn-title="My friend is {{ friend }}."> This is a directive. </p> <!-- Load scripts. --> <script type="text/javascript" src="../../vendor/jquery/jquery-2.1.0.min.js"></script> <script type="text/javascript" src="../../vendor/angularjs/angular-1.0.8.min.js"></script> <script type="text/javascript"> // Create an application module for our demo. var app = angular.module( "Demo", [] ); // -------------------------------------------------- // // -------------------------------------------------- // // I control the root of the application. app.controller( "AppController", function( $scope, $parse ) { $scope.friend = "Sarah"; } ); // -------------------------------------------------- // // -------------------------------------------------- // // I am the directive that tests the attribute interpolation to see when the // interpolated value is available to the link function. app.directive( "bnTitle", function() { // I bind the JavaScript events to the scope. function link( $scope, element, attributes ) { // Check initial value. console.log( "On link load:", attributes.bnTitle ); // Watch the value over time. attributes.$observe( "bnTitle", function( newValue, oldValue ) { console.log( "On observe:", newValue ); } ); } // Return the directive configuration. return({ link: link, restrict: "A" }); } ); </script> </body> </html>

As you can see, we are logging the attribute value - bnTitle - directly inside the synchronously-executed link() function body as well as inside the asynchronously-executed $observe() handler. When we run this code, we get the following console log output:

On link load: undefined

On observe: My friend is Sarah.

As you can see, the interpolated attribute value in unavailable until we are inside the $observe() callback.

By default, the priority of our "bnTitle" directive is 0. This is a lower priority than interpolation, which is 100. If I were to set my directive to be priority 101 (ie, a higher priority than interpolation), I end up getting the following console log output:

On link load: My friend is {{ friend }}.

On observe: My friend is Sarah.

In this case, both my directive and the interpolation directive are executing in the post-linking phase; but, mine is linking first. This means that I have access to the attribute value before the interpolation directive sets it to "undefined". However, I still don't get the fully-interpolated value until I'm inside the $observe() callback handler.

Now, let's contrast this with newer versions of AngularJS (ie, post 1.2). The general execution of directives is still the same - it has a compilation/pre-linking phase followed by a post-linking phase; but the details have changed a bit. Now, directives are compiled and pre-linked in priority order (ie, highest priority first); but, the post-linking phase takes place in reverse priority order.

In AngularJS 1.2, attribute interpolation now has the following characteristics:

It has priority 100.

It is configured explicitly in a pre-link function (ie, pre-linking phase).

It sets the initial value of the attribute to the result of the interpolation expression.

It sets subsequent changes in interpolated value inside the $observe() handler.

The most important take-aways for the latest versions of AngularJS are that attribute interpolation is done in the pre-linking phase and, perhaps more importantly, it sets the initial value of the attribute to the result of the interpolation. This means that when your directive is linked (most likely in the post-linking phase), the attribute value should already be interpolated.

And, in fact, when we run the same exact code as above, using AngularJS 1.2.16, we get the following console log output:

On link load: My friend is Sarah.

On observe: My friend is Sarah.

As you can see, the interpolated attribute value is now immediately available inside our link() function body.

Again, the default priority of our directive is 0. But this time, if I change the priority, it doesn't affect the outcome. This is because our directive is linking in the post-linking phase whereas interpolation is linking in the pre-linking phase. This means that the interpolated value will be available no matter what our priority is.

Of course, if we switch our directive to use the pre-linking phase, priority will become an issue. But, most directives execute in the post-linking phase with the default priority of zero.

No matter what version of AngularJS you use, you still need to use the $observe() method to monitor attribute interpolation values over time. But, if you're using AngularJS 1.2 or later, you can safely query the interpolated attribute values directly in your link() function body. This is kind of awesome. And, it can actually make your directive logic a good bit easier to understand.

Tweet This Interesting post by @BenNadel - Looking At Attribute Interpolation Workflow Changes In AngularJS Woot woot — you rock the party that rocks the body!







