2

I have several arbitrary number of menu items on my page. Simplified they look like this.

<a href="" class="menu-item" ng-click="...">...</a>

I would like to add a particular class to an item that is being clicked so that its state changes compared to others.

I know I should do it sugin this kind of a pattern:

<a href=""
   class="menu-item"
   ng-class="{ active: clicked }"
   ng-click="clicked = true">
...</a>

but the problem is that I can't use a single variable as I have an arbitrary number of items. I should either use X number of variables or use an array. But in any case how would I know which item goes with each variable/array index unless I manually enter those indices by hand?

What I'm missing

I'm missing element reference that I could use in ng-click so I could add a particular class on element itself. But that would somewhat bind $scope and UI even though I wouldn't be using any $scope function that would manipulate UI. I'd do it all in the view...

How am I supposed to do this?

4
  • Is it inside a ng-repeat ? How do you generate your "arbitrary number of items" ? Commented Jan 22, 2014 at 16:04
  • @FlorianF. no it's not inside an ng-repeat I know I could use $index in such case. I have several page sections where these item reside and number of them changes according to routing view. Some are always present others are loaded as per ng-view. Commented Jan 22, 2014 at 16:07
  • 1
    Have you looked into directive? This is a perfect case for a directive. Commented Jan 22, 2014 at 16:12
  • Was gonna suggest a directive too but it seems a bit overkill for a use case like this one... Commented Jan 22, 2014 at 16:13

2 Answers 2

3

A directive that solves this problem:

app.directive("markable", function() {
    return {
        scope: {}, // create isolated scope, so as not to touch the parent
        template: "<a href='#' ng-click='mark()' ng-class='{ active: marked }'><span ng-transclude></span></a>",
        replace: true,
        transclude: true,
        link: function(scope, elem, attrs) {
            scope.marked = false;
            scope.mark = function() {
                scope.marked = true;
            };
        }
    };
});

Use it as:

<a markable>Mark me</a>

Relevant fiddle: http://jsfiddle.net/9HP99/

The advantage of a directive is that it is very easy to use.


The same with a DOM-manupulating directive, through Angular's jQuery-ish interface:

app.directive("markable", function() {
    return {
        link: function(scope, elem, attrs) {
            elem.on("click", function() {
                elem.addClass("active");
            });
        }
    };
});

Usage:

<a href="#" markable>Mark me</a>

Fiddle: http://jsfiddle.net/atE4A/1/

Sign up to request clarification or add additional context in comments.

5 Comments

This is something I just started to write myself as well. But I would need to to the same on a non-replaced element. I would like to manipulate existing element's classes.
In fact doing so for a non-replaced element is easier, but dirtier. All it takes is some DOM manipulation (the dirty part). I am updating the answer...
I've done the same thing. Now what I'm wondering is whether this is slower compared to pure jQuery plugin that would do the same thing? Ok I'd have to handle clicks on some container element in order to handle future links, but performance wise? Which one is better? I'm leaning toward replacing my directive with a jQuery plugin.
You are right. A couple of points: (1) Generally placing one event handler in the parent element instead of one per child is better performance-wise. But you can always place the appropriate directive in the parent element. (2) Will you be having 1000s of elements? If not the anticipated performance hit per element will be negligible and not worth the dirtier code (e.g. manual $apply()) IMO.
These are pure routing links. No particular data binding, just UI navigation aid. So there wouldn't be any particular $apply calls. And you're correct. There's always just a handful of these elements...
0

You could do something like this:

<a href="..." ng-click="setItem($event)">...</a>

then you can use this.ClassName to get nth item.

But if you # of items is arbitrary, you may consider trying to format the list in a way where you could use ng-repeat, then it would be as easy as this:

<a href="..." ng-click="setItem($index)">...</a>

5 Comments

unfortunately this refers to current $scope. No element in that reference.
:active also doesn't do the trick, because this particular pseudo class is only applied between my mouse down and up events. It doesn't persist after I've lifted mouse button on the link...
I edited my answer to $event instead of this. $event.currentTarget should give you the element. Also it wasn't clear from your question what you meant by "state changes"
but this means that I would be manipulating UI in my controller is I used $event.currentTarget in scope's setItem function. That beats the separation of concerns that Angular brings us.
Like I said in my answer, you might want to consider changing things around a bit so you can use ng-repeat. Otherwise, while a "separation of concerns" is nice, Angular can't do everything.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.