207

Does AngularJS help in any way with setting an active class on the link for the current page?

I imagine there is some magical way this is done, but I can't seem to find.

My menu looks like:

 <ul>
   <li><a class="active" href="/tasks">Tasks</a>
   <li><a href="/actions">Tasks</a>
 </ul>

and I have controllers for each of them in my routes: TasksController and ActionsController.

But I can't figure out a way to bind the "active" class on the a links to the controllers.

Any hints?

Blackhole
  • 20,129
  • 7
  • 70
  • 68
Andriy Drozdyuk
  • 58,435
  • 50
  • 171
  • 272

29 Answers29

267

on view

<a ng-class="getClass('/tasks')" href="/tasks">Tasks</a>

on controller

$scope.getClass = function (path) {
  return ($location.path().substr(0, path.length) === path) ? 'active' : '';
}

With this the tasks link will have the active class in any url that starts with '/tasks'(e.g. '/tasks/1/reports')

Terry
  • 14,529
  • 13
  • 63
  • 88
Renan Tomal Fernandes
  • 10,978
  • 4
  • 48
  • 31
  • 4
    This would end up matching both "/" and "/anything" or if you have multiple menu items with similar urls, like "/test", "/test/this", "/test/this/path" if you were on /test, it would highlight all of those options. – Ben Lesh May 29 '13 at 02:34
  • 3
    I've changed this to if ($location.path() == path) and ,y path is "/blah" etc – Tim Jun 29 '13 at 06:55
  • what do I do if my route is like /poo/{bar}/foo – arg20 Oct 11 '13 at 16:56
  • 114
    I prefer the notation `ngClass="{active: isActive('/tasks')}`, where `isActive()` would return a boolean as it decouples the controller and the markup/styling. – Ed_ Oct 28 '13 at 12:58
  • 6
    Just in case anyone was wondering about the code to not double up if the path is "/", this is it (sorry for the formatting): $scope.getClass = function (path) { if ($location.path().substr(0, path.length) == path) { if (path == "/" && $location.path() == "/") { return "active"; } else if (path == "/") { return ""; } return "active" } else { return "" } } –  Nov 15 '13 at 08:08
  • 1
    EdHinchliffe already pointed out that this mixes markup and logic. It also leads to duplicating of the path and could therefor be prone to copy & paste errors. I've found that the directive approach by @kfis, although more lines, is more reusable and keeps the markup cleaner. – A. Murray Dec 18 '13 at 12:15
  • 1
    Thank you for a simple solution. I ended up adding a second optional parameter to the getClass function, a boolean which decides if it should be an exact match ($location.path() == path) or like the example in this answer. – Hans Westman Mar 11 '14 at 08:33
  • 1
    @EdHinchliffe 'ng-class'. – user3475602 Jun 16 '15 at 07:48
  • Another option could be `return $location.path().startsWith(path) ? 'active' : '';`. I prefer the @EdHinchliffe method of using the isActive boolean though – AlexanderD Sep 15 '16 at 15:03
  • None of the method is working for me. https://stackoverflow.com/questions/55026227/highlight-current-menu-using-angularjs here is my question. – Adarsh Singh Mar 07 '19 at 04:15
86

I suggest using a directive on a link.

But its not perfect yet. Watch out for the hashbangs ;)

Here is the javascript for directive:

angular.module('link', []).
  directive('activeLink', ['$location', function (location) {
    return {
      restrict: 'A',
      link: function(scope, element, attrs, controller) {
        var clazz = attrs.activeLink;
        var path = attrs.href;
        path = path.substring(1); //hack because path does not return including hashbang
        scope.location = location;
        scope.$watch('location.path()', function (newPath) {
          if (path === newPath) {
            element.addClass(clazz);
          } else {
            element.removeClass(clazz);
          }
        });
      }
    };
  }]);

and here is how it would be used in html:

<div ng-app="link">
  <a href="#/one" active-link="active">One</a>
  <a href="#/two" active-link="active">One</a>
  <a href="#" active-link="active">home</a>
</div>

afterwards styling with css:

.active { color: red; }
Preview
  • 35,317
  • 10
  • 92
  • 112
kfis
  • 4,739
  • 22
  • 19
  • I am not sure what you mean by "watch out for hashbangs". It seems like it would always work. Could you provide a counter-example? – Andriy Drozdyuk Mar 19 '13 at 04:36
  • 7
    If you are trying to use Bootstrap and need to set based on the hash of an a's href within a li, then use `var path = $(element).children("a")[0].hash.substring(1);`. This will work for a style like `
  • Dashboard
  • ` – Dave Sep 02 '13 at 00:33
  • 2
    I would change `scope.$watch('location.path()', function(newPath) {` for `scope.$on('$locationChangeStart', function(){`. – sanfilippopablo Dec 31 '13 at 19:13
  • 2
    If you're using ng-href just change: `var path = attrs.href;` to `var path = attrs.href||attrs.ngHref;` – William Neely May 30 '14 at 11:19
  • If you use Bootstrap and need to put the active class on the `
  • `, you can change `element.addClass(clazz);` to `element.parent().addClass(clazz);`
  • – JamesRLamar Jun 10 '14 at 23:25
  • Better we can use location.url() instead if location.path() in order to capture request parameters also. – Tamal Kanti Nath Oct 11 '14 at 13:51