Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at CFinNC 2009 (Raleigh, North Carolina) with: Simon Free

Using Start/End Directive-Ranges In AngularJS 1.2

By Ben Nadel 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.




Reader Comments

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

Reply to this Comment

@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.

Reply to this Comment

@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.

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.