Binding A Single Directive To Multiple Priorities On The Same Element In AngularJS
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!
Want to use code from this post? Check out the license.
Reader Comments
@All,
FYI, I tested this back to AngularJS 1.0.8 and it seems to work nicely.
Nice!
This is awesome Ben, 1001 and 999 ftw ;)
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.
This is really cool Ben. Thanks for sharing!
@Josh,
Thanks my man! Hopefully we can come up with some good applications for this kind of stuff in InVision!
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.
@All,
Here is a real-world application for this approach, which is more of a performance optimization than anything else:
www.bennadel.com/blog/2712-refactoring-bnrepeatswitch-to-use-a-multi-priority-directive-in-angularjs.htm
As with this blog post, it binds to both "sides" of the native ngRepeat directive.
@Julian,
Thank you so much! I really appreciate the kind words :D
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........