AngularJS Directive to add class on click, but remove and add it to another element if clicked

27,467

Solution 1

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%).

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

Share:
27,467
Mr. BigglesWorth
Author by

Mr. BigglesWorth

Trying to be a better javascript developer. Hungry to learn and grow my skill set.

Updated on July 09, 2022

Comments

  • Mr. BigglesWorth
    Mr. BigglesWorth almost 2 years

    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;
    
          });
    
    
        }
      };
    });