67
votes

Is there a more "angular" way of selecting DOM elements inside a directive template? For example, say you have this directive:

app.directive("myDirective", function() {
    return {
        template: '<div><ul><li ng-repeat="item in items"></ul></div>',
        link: function(scope, element, attrs) {
            var list = element.find("ul");
        }
    }
});

I used the jQuery style selector to get a hold of the DOM <ul> element rendered in my template. Is there a better way to do this?

3
In state, I don't think there is a better way to do that. But there is probably a better choice than select an element. What's your goal, exactly ? - Blackhole
Nothing in particular. This is just a common thing I keep running in to. I will need to access the DOM elements inside of a directive template for whatever reason, but using jQuery style selectors just seems wrong. - Dustin
@Dustin like Blackhole mentioned, in 90% of the directives you dont need to access the DOM element through a selector - which is also the reason why Angular's JQueryLite supports very few and basic selectors. If you can let us know what you are attempting to achieve then someone would suggest a better way of doing it. - ganaraj
ganaraj, I don't know that I have anything specific right now. I have been needing these DOM elements for various reasons (e.g. applying CSS styles to a list after ngRepeat has finished). I was just curious if anyone had an elegant solution to this need... creating a separate directive perhaps? - Dustin
applying CSS styles to a list after ngRepeat has finished ? cant you use ng-class / ng-style with ng-repeat for this? The reason for questioning your questions is that , the premise for "the angular way of selecting DOM elements" itself seems wrong, since the angular way ( in most cases ) is not selecting DOM elements.. - ganaraj

3 Answers

42
votes

You could write a directive for this, which simply assigns the (jqLite) element to the scope using an attribute-given name.

Here is the directive:

app.directive("ngScopeElement", function () {
  var directiveDefinitionObject = {

    restrict: "A",

    compile: function compile(tElement, tAttrs, transclude) {
      return {
          pre: function preLink(scope, iElement, iAttrs, controller) {
            scope[iAttrs.ngScopeElement] = iElement;
          }
        };
    }
  };

  return directiveDefinitionObject;
});

Usage:

app.directive("myDirective", function() {
    return {
        template: '<div><ul ng-scope-element="list"><li ng-repeat="item in items"></ul></div>',
        link: function(scope, element, attrs) {
            scope.list[0] // scope.list is the jqlite element, 
                          // scope.list[0] is the native dom element
        }
    }
});

Some remarks:

  • Due to the compile and link order for nested directives you can only access scope.list from myDirectives postLink-Function, which you are very likely using anyway
  • ngScopeElement uses a preLink-function, so that directives nested within the element having ng-scope-element can already access scope.list
  • not sure how this behaves performance-wise
48
votes

I don't think there is a more "angular way" to select an element. See, for instance, the way they are achieving this goal in the last example of this old documentation page:

{
     template: '<div>' +
    '<div class="title">{{title}}</div>' +
    '<div class="body" ng-transclude></div>' +
    '</div>',

    link: function(scope, element, attrs) {
        // Title element
        var title = angular.element(element.children()[0]),
        // ...
    }
}
5
votes

This answer comes a little bit late, but I just was in a similar need.

Observing the comments written by @ganaraj in the question, One use case I was in the need of is, passing a classname via a directive attribute to be added to a ng-repeat li tag in the template.

For example, use the directive like this:

<my-directive class2add="special-class" />

And get the following html:

<div>
    <ul>
       <li class="special-class">Item 1</li>
       <li class="special-class">Item 2</li>
    </ul>
</div>

The solution found here applied with templateUrl, would be:

app.directive("myDirective", function() {
    return {
        template: function(element, attrs){
            return '<div><ul><li ng-repeat="item in items" class="'+attrs.class2add+'"></ul></div>';
        },
        link: function(scope, element, attrs) {
            var list = element.find("ul");
        }
    }
});

Just tried it successfully with AngularJS 1.4.9.

Hope it helps.