5

I've got a simple directive that I use to add a class on click and remove it from the element if clicked again. However I'd like to refactor it for a more common use in generic menus. Instead if a <li> element is clicked that is not the current active element, it should remove it from the current element and place it on the new one. Basically I want to add an "active" class to the <li> element that is currently active.

In my menu I have:

<ul>
    <li><a swapit ng-click="lol(stillgot)" class="select-show">Still Got Game</a></li>
    <li><a swapit ng-click="lol(thick)" class="select-show">TnT</a></li>
    <li><a swapit ng-click="lol(seldon)" class="select-show">Seldon</a></li>
    <li><a swapit ng-click="lol(hit)" class="select-show">HitMan</a></li>
    <li><a swapit ng-click="lol(community)" class="select-show">Community</a></li>
</ul>

.directive('swapit', function() {

  return {
    restrict : 'A',

    link : function(scope, elem) {

      var currentState = true;

      elem.on('click', function() {
        console.log('You clicked me!');

        if(currentState === true) {
          console.log('It is on!');
          angular.element(elem).addClass('active');
        } else {
          console.log('It is off!');
          angular.element(elem).removeClass('active');
        }

        currentState = !currentState;

      });


    }
  };
});

2 Answers 2

12

You can avoid DOM manipulation and allow for reuse if you utilize isolate scope and ng-class:

Directive:

.directive('swapit', function(){
  return {
    restrict: 'E',
    replace: true,
    transclude: true,
    scope: {
      active: '='
    },
    template: '<a ng-click="active = $id" ng-class="{active: $id === active}" ng-transclude></a>'
  }
})

HTML:

<swapit active="active">Still got game</swapit>
<swapit active="active">TnT</swapit>
...

By using isolate scope, each directive element will have its own scope with a unique ID, which can be accessed through scope.$id. When a directive element is clicked on, you can assign this value to an active scope variable, which is shared between your directives.

ng-click="active = $id"

Then, you can use ng-class with an expression which determines whether each directive element's scope ID matches that of the currently active scope ID:

ng-class="{active: $id === active}"

View this demo, which more closely matches your implementation (though not 100%).

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

3 Comments

How would this affect my current senerio though where I've already got an ng-click bound to a function that loads in other content?
Take a look at the demo: you can still have ng-click handle other tasks
In the demo, I still call the lol function on click by using the & isolate scope setting on an attribute, which allows you to make internal calls to parent functions.
2

Here is a directive that does that:


BTW, you can specify a name for the togglable group, so you can have multiple "menus" on the same page (without one interfering with the other). E.g.:

<li ... toggle="site-menu">Page 1</li>
<li ... toggle="site-menu">Page 2</li>
...
<li ... toggle="whatever-submenu">Option 1</li>
<li ... toggle="whatever-submenu">Option 2</li>

app.directive('toggle', function () {
  var TOGGLE_CLASS = 'selected';
  var groups = {};

  function addElement(groupName, elem) {
    var list = groups[groupName] || (groups[groupName] = []);
    if (list.indexOf(elem) === -1) {
      list.push(elem);
    }
  }

  function removeElement(groupName, elem) {
    var list = groups[groupName] || [];
    var idx = list.indexOf(elem);
    if (idx !== -1) {
      list.splice(idx, 1);
    }
  }

  function setActive(groupName, elem) {
    angular.forEach(groups[groupName], function (el) {
      el.removeClass(TOGGLE_CLASS);
    });
    elem.addClass(TOGGLE_CLASS);
  }

  return {
    restrict: 'A',
    link: function postLink(scope, elem, attrs) {
      var groupName = attrs.toggle || 'default';
      addElement(groupName, elem);

      elem.on('click', function () {
        setActive(groupName, elem);
      });

      scope.$on('$destroy', function () {
        removeElement(groupName, elem);
      });
    }
  };
});

You can use it like this:

<a href="" toggle="test" ng-repeat="x in [1,2,3,4,5]">Link {{$index + 1}}</a>

See, also, this short demo.

Comments

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.