Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Katie Glenn
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Katie Glenn

Overriding Directive Definitions In AngularJS

By
Published in Comments (4)

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.

Want to use code from this post? Check out the license.

Reader Comments

18 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!

15,841 Comments

@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

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel