Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at CFinNC 2009 (Raleigh, North Carolina) with: Simon Free and Todd Sharp and Shannon Hicks and Charlie Arehart and Sean Corfield and Jason Dean
Ben Nadel at CFinNC 2009 (Raleigh, North Carolina) with: Simon Free@simonfree ) , Todd Sharp@cfsilence ) , Shannon Hicks@iotashan ) , Charlie Arehart@carehart ) , Sean Corfield@seancorfield ) , and Jason Dean@JasonPDean )

Binding A Single Directive To Multiple Priorities On The Same Element In AngularJS

By Ben Nadel on

Last night, I was looking through the AngularJS source code when I noticed that the ngInclude directive was defined twice. At first, I had no idea what to make of this craziness; but then, I realized that each version of the directive was given a different priority. I had no idea that this was possible in AngularJS - giving a single directive different priorities; but, upon further investigation, it totally works!


 
 
 

 
 
 
 
 

Run this demo in my JavaScript Demos project on GitHub.

To test this multi-priority directive feature, I wanted to created a directive that would execute on either "side" of the native ngRepeat directive. This would allow the same AngularJS directive to have access to the ngRepeat element both before and after transclusion.

Since ngRepeat executes with priority 1000, I'm going to define a single directive that uses priorities 1001 (before ngRepeat) and 999 (after ngRepeat).

  • <!doctype html>
  • <html ng-app="Demo">
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • Binding A Single Directive To Multiple Priorities On The Same Element In AngularJS
  • </title>
  •  
  • <style type="text/css">
  •  
  • a[ ng-click ] {
  • cursor: pointer ;
  • text-decoration: underline ;
  • }
  •  
  • li.active {
  • background-color: #FFD0D0 ;
  • }
  •  
  • </style>
  • </head>
  • <body ng-controller="AppController">
  •  
  • <h1>
  • Binding A Single Directive To Multiple Priorities On The Same Element In AngularJS
  • </h1>
  •  
  • <ul>
  • <!--
  • The bnFriends directive is going to be defined at two different priorities
  • that allow the same directive to link both BEFORE and AFTER the ngRepeat
  • directive does its transclusion.
  • -->
  • <li
  • bn-friends
  • ng-repeat="friend in friends">
  •  
  • {{ friend.name }}
  •  
  • </li>
  • </ul>
  •  
  •  
  • <!-- 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.2.22.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 ) {
  •  
  • $scope.friends = [
  • {
  • id: 1,
  • name: "Sarah"
  • },
  • {
  • id: 2,
  • name: "Tricia"
  • },
  • {
  • id: 3,
  • name: "Joanna"
  • },
  • {
  • id: 4,
  • name: "Kim"
  • }
  • ];
  •  
  • }
  • );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Typically, we would only define a directive once; however, by defining it
  • // twice, with different configurations, we can actually bind a "single" directive
  • // to two different priorities in the same compilation and linking phase.
  • // --
  • // NOTE: This directive will execute a priority 1001 and 999; since the ngRepeat
  • // directive executes at priority 1000, this directive - bnFriends - will compile
  • // and link on both "sides" of the ngRepeat directive.
  • app
  • .directive(
  • "bnFriends",
  • function( $compile ) {
  •  
  • // I compile the current element. Since this executes with priority
  • // 1001, it will be able to compile/alter the element BEFORE ngRepeat
  • // compiles and transcludes it.
  • function compile( tElement, tAttributes ) {
  •  
  • console.info( "High priority compiling" );
  •  
  • tElement.text( tElement.text() + " is my good friend!" );
  •  
  • return( link );
  •  
  •  
  • // Because this executes before ngRepeat, it means that we have
  • // an opportunity to set up a controller that will be available
  • // to the post-ngRepeat phase.
  • function link( scope, element, attributes, controller ) {
  •  
  • console.info( "High priority linking" );
  •  
  • controller.message = "hello world";
  •  
  • };
  •  
  • }
  •  
  •  
  • // Return the directive configuration.
  • // --
  • // NOTE: This version of the directive, with priority 1001, will
  • // execute before the ngRepeat directive.
  • return({
  • compile: compile,
  • controller: angular.noop,
  • priority: 1001,
  • restrict: "A"
  • });
  •  
  • }
  • )
  • .directive(
  • "bnFriends",
  • function() {
  •  
  • // Since this executes with a lower priority than ngRepeat, it means
  • // that this version of the directive will link for every node that
  • // the ngRepeat directive will transclude and link.
  • function link( scope, element, attributes, controller ) {
  •  
  • console.info(
  • "Low priority linking [ %d ][ %s ]",
  • scope.$index,
  • controller.message
  • );
  •  
  • // Set up some simple mouse-behaviors for testing.
  • element.hover(
  • function handleMouseEnter() {
  •  
  • element.addClass( "active" );
  •  
  • },
  • function handleMouseLeave() {
  •  
  • element.removeClass( "active" );
  •  
  • }
  • );
  •  
  • }
  •  
  •  
  • // Return the directive configuration. Notice that we are "requiring"
  • // the controller that our other version instantiated and defined.
  • // --
  • // NOTE: This version of the directive, with priority 999, will
  • // execute after the ngRepeat directive.
  • return({
  • link: link,
  • priority: 999,
  • require: "bnFriends",
  • restrict: "A"
  • });
  •  
  • }
  • )
  • ;
  •  
  • </script>
  •  
  • </body>
  • </html>

As you can see, the directive bnFriends, provides a compile and a link function that will run before the ngRepeat priority; and, it provides a link function that will run after the ngRepeat priority. Furthermore, the bnFriends directive defines a controller that allows both priorities of the directive to maintain communication.

When we run the above code, we get the following console output:

High priority compiling
High priority linking
Low priority linking [ 0 ][ hello world ]
Low priority linking [ 1 ][ hello world ]
Low priority linking [ 2 ][ hello world ]
Low priority linking [ 3 ][ hello world ]

As you can see, the "high priority" version of the directive compiled and linked once before the ngRepeat directive; then, the "low priority" version of the same directive linked after the ngRepeat directive, running once per cloned and linked element.

The benefit of this might not be obvious; but, when I saw this, I immediately thought of my ngRepeat-optimized "switch" directive that needed access to both "sides" of the ngRepeat directive. Being able to define the same directive at multiple priorities would certainly simplify such approaches.

AngularJS, you're always surprising me with your awesome sauce!




Reader Comments

Thanks guys! Glad you like. Reading through the AngularJS source code is [often] like reading a foreign language :) I'm sure there's all sorts of interesting techniques that they use that I can't even being to understand.

Reply to this Comment

@Josh,

Thanks my man! Hopefully we can come up with some good applications for this kind of stuff in InVision!

Reply to this Comment

This is probably one of the best mentions of this topic I've seen in quite a while. It's obvious that your knowledge of the subject is deep and this made for a very interesting read.

Reply to this Comment

Hi Ben !!!
I was working on the same code.But I really didn't find solution for my queries.Is it possible to to access different controllers information in single directive using angular js........

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.