Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at the jQuery Conference 2010 (Boston, MA) with: Rachel Makrucki
Ben Nadel at the jQuery Conference 2010 (Boston, MA) with: Rachel Makrucki ( @m_rookie )

Overriding Directive Definitions In AngularJS

By Ben Nadel on

In the past, I've demonstrated that you can bind multiple directives to the same element (or attribute) in AngularJS. This opens up some really exciting possibilities in terms of binding a "single directive" to multiple priorities in the same compiling and linking life cycle. But, last night, when I was looking at the ngTouch module, it occurred to me that ngTouch isn't augmenting the ngClick directive - it's completely overriding it. Not knowing that this was possible, I dug through the source-code and found out that this override is being performed through the use of a directive decorator.


 
 
 

 
 
 
 
 

Run this demo in my JavaScript Demos project on GitHub.

We've looked at the use of decorators in the configuration blocks as well when using the new module.decorator() method introduced in AngularJS 1.4. But, in all of these cases I've only ever considered using these decorators with services. It turns out, however, that these decorator methods can be used with directives as well. But, unlike a service, you have to suffix the decorator name with "Directive". So, if you want to decorate the "script" directive, for example, you would have to define a decorator for "scriptDirective".

I think Ward Bell may have actually brought this up on an episode of Adventures in AngularJS a while back. But, I think he did so in the context of unit testing. And, since I know next-to-nothing about unit testing, I don't think that I really connected with what he was saying.

When you create a decorator, for a directive, the $delegate that gets passed-in is an array of directive definitions and bindings for that directive. You are expected to return a similar array. That can be the same array. It can be a new array. It can be an empty array; or, it can be a paired-down array, as is the case with the ngTouch module (they splice-out the first item, which is the core ngClick directive).

To see this in action, I've created a demo in which I define three different directive bindings for the "section" element. However, I'm also defining a decorator for this directive that randomly selects and returns only one of the three directive definitions:

  • <!doctype html>
  • <html ng-app="Demo">
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • Overriding Directive Definitions In AngularJS
  • </title>
  • </head>
  • <body>
  •  
  • <h1>
  • Overriding Directive Definitions In AngularJS
  • </h1>
  •  
  • <section>
  • This is a directive &lt;section&gt;!
  • </section>
  •  
  • <p>
  • <em>See console for output.</em>
  • </p>
  •  
  •  
  • <!-- Load scripts. -->
  • <script type="text/javascript" src="../../vendor/angularjs/angular-1.4.5.min.js"></script>
  • <script type="text/javascript">
  •  
  • // Create an application module for our demo.
  • angular.module( "Demo", [] );
  •  
  •  
  • // --------------------------------------------------------------------------- //
  • // --------------------------------------------------------------------------- //
  •  
  •  
  • // *** NOTE: We are defining three directives on the SAME ELEMENT. ***
  •  
  • // First directive on SECTION element.
  • angular.module( "Demo" )
  • .directive(
  • "section",
  • function sectionDirective() {
  •  
  • // Return the directive configuration object.
  • return({
  • link: function( scope, element, attributes ) {
  •  
  • console.log( "First directive linked." );
  •  
  • },
  • restrict: "E",
  •  
  • // This is not part of the core directive definition object; but,
  • // it will be available within the .decorator() method, which
  • // may be useful for pairing-down the collection.
  • bensLabel: "First-by-Ben"
  • });
  •  
  • }
  • )
  • .directive(
  • "section",
  • function sectionDirective() {
  •  
  • // Return the directive configuration object.
  • return({
  • link: function( scope, element, attributes ) {
  •  
  • console.log( "Second directive linked." );
  •  
  • },
  • restrict: "E",
  •  
  • // This is not part of the core directive definition object; but,
  • // it will be available within the .decorator() method, which
  • // may be useful for pairing-down the collection.
  • bensLabel: "Second-by-Ben"
  • });
  •  
  • }
  • )
  • .directive(
  • "section",
  • function sectionDirective() {
  •  
  • // Return the directive configuration object.
  • return({
  • link: function( scope, element, attributes ) {
  •  
  • console.log( "Third directive linked." );
  •  
  • },
  • restrict: "E",
  •  
  • // This is not part of the core directive definition object; but,
  • // it will be available within the .decorator() method, which
  • // may be useful for pairing-down the collection.
  • bensLabel: "Third-by-Ben"
  • });
  •  
  • }
  • )
  • ;
  •  
  •  
  • // --------------------------------------------------------------------------- //
  • // --------------------------------------------------------------------------- //
  •  
  •  
  • // CAUTION: Because I am using the .decorator() module method, this call has
  • // to come AFTER the value that it is decorating. This is different than the
  • // .config() block, which can be invoked at any point in the module definition.
  • // --
  • // SEE: http://www.bennadel.com/blog/2870-using-module-decorator-in-angularjs-1-4.htm
  • angular.module( "Demo" ).decorator(
  • "sectionDirective",
  • function sectionDirectiveDecorator( $delegate ) {
  •  
  • console.log( ". . . . . . . . . . . . . ." );
  • console.log( "There are %s matching directives.", $delegate.length );
  • console.log( "Selecting a random one." );
  • console.log( ". . . . . . . . . . . . . ." );
  •  
  • var randomIndex = Math.floor( Math.random() * $delegate.length );
  • var randomDirective = $delegate[ randomIndex ];
  •  
  • // Demonstrating that our custom "label" field is available on the
  • // object in the $delegate collection.
  • console.log( "That maths randomly chose: %s.", randomDirective.bensLabel );
  • console.log( ". . . . . . . . . . . . . ." );
  •  
  • // Return a new array that contains only the randomly-selected version
  • // of the directive.
  • return( [ randomDirective ] );
  •  
  • }
  • );
  •  
  • </script>
  •  
  • </body>
  • </html>

As you can see, the .decorator() receives the array of directive bindings as the $delegate. Then, it returns a new array with a randomly-selected binding. And, when we run the above code, we get the following output:


 
 
 

 
 Overriding directive definitions in AngularJS.  
 
 
 

As you can see, only one of the [randomly selected] "section" directives was ever linked.

While there aren't that many use-cases for this, outside of testing (ala Ward Bell) and "upgrading" existing directives (ala ngTouch), it's good to know that it is possible. Sometimes, you have to understand that something is possible before you can learn to innovate with it.

Tweet This Deep thoughts by @BenNadel - Overriding Directive Definitions In AngularJS Thanks my man — you rock the party that rocks the body!



Reader Comments

Opens up the world of directives, doesn't it! :-)

This is exactly what I was talking about on that podcast, Ben. But I was waving my arms in the air as if people could see me and never got around to writing it down.

You've done a great job of laying it out clearly so people can benefit. Kudos!

Reply to this Comment

@Ward,

It is definitely interesting. Makes me want to learn more about this whole "Testing" thing you always seem to be going on about :D

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.