Skip to main content
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with: Oguz Demirkapi
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with: Oguz Demirkapi ( @demirkapi )

Using Start/End Directive-Ranges In AngularJS 1.2

By on

It use to be that AngularJS directives were applied to a single DOM (Document Object Model) node (and its descendants). With AngularJS 1.2, however, you can now apply a directive to a "range" of sibling DOM nodes. To do this, you append "-start" and "-end" to the directive element attributes. I am not yet sure how I would use this feature; but, I wanted to take a quick look at how it works.

View this demo in my JavaScript-Demos project on GitHub.

Imagine that I have a directive attribute, "bn-thing". This would be applied to a single DOM node. If I wanted to apply it to a range of DOM nodes, however, I could alter it such that the first, or "start" DOM node, would have the directive attribute:

bn-thing**-start**

Then, I could add an attribute to the "end" DOM node:

bn-thing**-end**

Notice that I have append "-start" and "-end" to the directive, "bn-thing".

Normally, the compile and link functions receive a jQuery (or JQLite) reference that contains a single DOM node - the one on which the directive is being applied. With these attributes in place, however, both the compile and link functions receive a jQuery (or JQLite) reference that contains a collection of all the DOM nodes in between the "start" and "end" nodes, inclusive.

To explore this change, I created two directives - one that simply "links" the DOM nodes and another that compiles the DOM nodes. The simple one does nothing more than add a class - "item" - to all the DOM nodes in the directive "range." The complex one compiles the DOM, placing the elements inside a new Div container.

<!doctype html>
<html ng-app="Demo">
<head>
	<meta charset="utf-8" />

	<title>
		Using Start/End Directive-Ranges In AngularJS 1.2
	</title>

	<style type="text/css">

		div.container {
			border: 1px solid red ;
			margin: 16px 0px 16px 0px ;
			padding: 0px 16px 0px 16px ;
		}

		p.item {
			border: 1px solid gold ;
		}

	</style>
</head>
<body ng-controller="AppController">

	<h1>
		Using Start/End Directive-Ranges In AngularJS 1.2
	</h1>

	<!-- START: Simple Directive Test. -->
	<p bn-simple-start>
		Start Simple Directive
	</p>

	<p>
		... content...
	</p>

	<p bn-simple-end>
		End Simple Directive
	</p>
	<!-- END: Simple Directive Test. -->


	<!-- START: Simple Directive Test. -->
	<p bn-complex-start>
		Start Complex Directive
	</p>

	<p>
		... content...
	</p>

	<p bn-complex-end>
		End Complex Directive
	</p>
	<!-- END: Simple Directive Test. -->


	<!-- Load scripts. -->
	<script type="text/javascript" src="../../vendor/jquery/jquery-2.0.3.min.js"></script>
	<script type="text/javascript" src="../../vendor/angularjs/angular-1.2.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 ) { /* ... */ }
		);


		// -------------------------------------------------- //
		// -------------------------------------------------- //


		// I simply decorate the elements in the linking phase.
		app.directive(
			"bnSimple",
			function() {

				// I bind the UI to the $scope.
				function link( $scope, elements, attributes ) {

					console.log( "Simple Element(s):" );
					console.log( elements );

					elements.addClass( "item" );

				}


				// Return the directive configuration.
				return({
					link: link
				});

			}
		);


		// -------------------------------------------------- //
		// -------------------------------------------------- //


		// I transclude the set of elements in the directive, wrapping
		// them in a container before injecting them back into the DOM.
		app.directive(
			"bnComplex",
			function() {

				// Since we are transcluding the "element", there will
				// be no rendered DOM elements until the template
				// element is cloned and transcluded.
				function compile( tElement, tAttributes, transclude ) {

					// I bind the UI to the $scope.
					function link( $scope, element, attributes ) {

						transclude(
							$scope,
							function( clonedContents ) {

								clonedContents.addClass( "item" );

								var div = $( "<div />" )
									.addClass( "container" )
									.append( clonedContents )
									.insertAfter( element )
								;

							}
						);

					}

					return( link );

				}


				// Return the directive configuration.
				return({
					compile: compile,
					transclude: "element"
				});

			}
		);


	</script>

</body>
</html>

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

Start/End directives in AngularJS 1.2.

As you can see, when using the "-start" and "-end" directive modifiers, the link function is given a collection of DOM nodes instead of a single DOM nodes. Other than that, however, the directives basically work the same way as they did before. Of course, "-start" and "-end" are now special syntax tokens and cannot be used as part of single-point directives (which is a breaking change in AngularJS 1.2). Again, I'm not exactly sure how I would use this kind of a directive "range"; but, it's definitely interesting.

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

Reader Comments

4 Comments

Very informative ! Thanks. FYI, the transclude function that is passed to the compile function is deperecated.

15,674 Comments

@Sam,

Thanks for the insight - I did not know that was deprecated. I'll see if I can find the documentation on the new way to use transclude.

15,674 Comments

@All,

I just found the documentation that Sam was referring to. In the new $compile docs:

http://docs.angularjs.org/api/ng.$compile

... it states that the transclude passed to the compiler is deprecated:

Note: The transclude function that is passed to the compile function is deprecated, as it e.g. does not know about the right outer scope. Please use the transclude function that is passed to the link function instead.

Apparently, now, the link() function is actually passed a reference to the correct $scope instance:

function link( scope, iElement, iAttrs, controller, transcludeFn ) { ... }

... but, it looks like you can still override the $scope being passed to the transclude function, as I assume the ngRepeat directive is doing (with each repeat item).

Good to know! I think this makes a lot more sense - I've many times called the compile() function JUST to get access to the transclude.

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