¿Cómo resaltar un elemento del menú actual?

¿AngularJS ayuda de alguna manera con la configuración de una clase active en el enlace de la página actual?

Imagino que hay una forma mágica de hacerlo, pero parece que no puedo encontrarlo.

Mi menú se ve así:

   

y tengo controladores para cada uno de ellos en mis rutas: TasksController y ActionsController .

Pero no puedo encontrar una manera de vincular la clase “activa” en los enlaces a los controladores.

¿Algún consejo?

a la vista

 Tasks 

en el controlador

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

Con esto, el enlace de tareas tendrá la clase activa en cualquier url que comience con ‘/ tasks’ (por ejemplo, ‘/ tasks / 1 / reports’)

Sugiero usar una directiva en un enlace.

Pero aún no es perfecto. Cuidado con los hashbangs;)

Aquí está la directiva javascript for:

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

y así es como se usaría en html:

  

luego estilo con css:

 .active { color: red; } 

Aquí hay un enfoque simple que funciona bien con Angular.

  

Dentro de su controlador AngularJS:

 $scope.isActive = function (viewLocation) { var active = (viewLocation === $location.path()); return active; }; 

Este hilo tiene varias respuestas similares.

¿Cómo configurar la clase activa de barra de navegación bootstrap con Angular JS?

Solo para agregar mis dos centavos en el debate he hecho un módulo angular puro (no jQuery), y también funcionará con direcciones hash que contienen datos. (por ejemplo, #/this/is/path?this=is&some=data )

Simplemente agrega el módulo como una dependencia y auto-active a uno de los antepasados ​​del menú. Me gusta esto:

  

Y el módulo se ve así:

 (function () { angular.module('autoActive', []) .directive('autoActive', ['$location', function ($location) { return { restrict: 'A', scope: false, link: function (scope, element) { function setActive() { var path = $location.path(); if (path) { angular.forEach(element.find('li'), function (li) { var anchor = li.querySelector('a'); if (anchor.href.match('#' + path + '(?=\\?|$)')) { angular.element(li).addClass('active'); } else { angular.element(li).removeClass('active'); } }); } } setActive(); scope.$on('$locationChangeSuccess', setActive); } } }]); }()); 

(Por supuesto, puede usar la parte directiva)

También vale la pena notar que esto no funciona para hashes vacíos (ej. example.com/# o simplemente example.com ) necesita tener al menos example.com/#/ o simplemente example.com#/ . Pero esto sucede automáticamente con ngResource y similares.

Y aquí está el violín: http://jsfiddle.net/gy2an/8/

En mi caso, resolví este problema creando un controlador simple responsable de la navegación

 angular.module('DemoApp') .controller('NavigationCtrl', ['$scope', '$location', function ($scope, $location) { $scope.isCurrentPath = function (path) { return $location.path() == path; }; }]); 

Y simplemente agregando ng-class al elemento así:

  

Hay una directiva ng-class , que vincula la clase variable y css. También acepta el objeto (className vs bool value pairs).

Aquí está el ejemplo, http://plnkr.co/edit/SWZAqj

La respuesta de @ Renan-tomal-fernandes es buena, pero se necesita un par de mejoras para que funcione correctamente. Tal como estaba, siempre detectaría el enlace a la página de inicio (/) como desencadenado, incluso si estuviera en otra sección.

Así que lo mejoré un poco, aquí está el código. Trabajo con Bootstrap, por lo que la parte activa está en el elemento

  • lugar del .

    Controlador

     $scope.getClass = function(path) { var cur_path = $location.path().substr(0, path.length); if (cur_path == path) { if($location.path().substr(0).length > 1 && path.length == 1 ) return ""; else return "active"; } else { return ""; } } 

    Modelo

      

    Para usuarios de AngularUI Router:

      

    Y eso colocará una clase active en el objeto que se selecciona.

    Aquí está la solución que surgió después de leer algunas de las excelentes sugerencias anteriores. En mi situación particular, estaba tratando de usar el componente de tabs Bootstrap como mi menú, pero no quería usar la versión de UU angular porque quiero que las tabs actúen como un menú, donde cada pestaña es compatible con marcadores, en lugar de las tabs que actúan como navegación para una sola página. (Consulte http://angular-ui.github.io/bootstrap/#/tabs si le interesa saber qué aspecto tiene la versión de UB angular de las tabs de arranque).

    Realmente me gustó la respuesta de kfis sobre la creación de su propia directiva para manejar esto, sin embargo, parecía engorroso tener una directiva que debía colocarse en cada enlace. Así que he creado mi propia directiva angular que se coloca una vez en el ul . En caso de que alguien más esté tratando de hacer lo mismo, pensé que lo publicaría aquí, aunque como dije, muchas de las soluciones anteriores también funcionan. Esta es una solución un poco más compleja en lo que respecta al javascript, pero crea un componente reutilizable con un marcado mínimo.

    Aquí está el javascript para la directiva y el proveedor de rutas para ng:view :

     var app = angular.module('plunker', ['ui.bootstrap']). config(['$routeProvider', function($routeProvider) { $routeProvider. when('/One', {templateUrl: 'one.html'}). when('/Two', {templateUrl: 'two.html'}). when('/Three', {templateUrl: 'three.html'}). otherwise({redirectTo: '/One'}); }]). directive('navTabs', ['$location', function(location) { return { restrict: 'A', link: function(scope, element) { var $ul = $(element); $ul.addClass("nav nav-tabs"); var $tabs = $ul.children(); var tabMap = {}; $tabs.each(function() { var $li = $(this); //Substring 1 to remove the # at the beginning (because location.path() below does not return the #) tabMap[$li.find('a').attr('href').substring(1)] = $li; }); scope.location = location; scope.$watch('location.path()', function(newPath) { $tabs.removeClass("active"); tabMap[newPath].addClass("active"); }); } }; }]); 

    Luego en tu html simplemente:

       

    Aquí está el plunker para él: http://plnkr.co/edit/xwGtGqrT7kWoCKnGDHYN?p=preview .

    Puede implementar esto de manera muy simple, aquí hay un ejemplo:

      

    Y tu controlador debería ser esto:

     app.controller("MenuCtrl", function($scope, $location) { $scope.menuClass = function(page) { var current = $location.path().substring(1); return page === current ? "active" : ""; }; }); 

    utilice la directiva ui-sref-active de angular-ui-router https://github.com/angular-ui/ui-router/wiki/Quick-Reference#statename

    Tuve un problema similar con el menú ubicado fuera del scope del controlador. No estoy seguro si esta es la mejor solución o una recomendada, pero esto es lo que funcionó para mí. Agregué lo siguiente a la configuración de mi aplicación:

     var app = angular.module('myApp'); app.run(function($rootScope, $location){ $rootScope.menuActive = function(url, exactMatch){ if (exactMatch){ return $location.path() == url; } else { return $location.path().indexOf(url) == 0; } } }); 

    Entonces en la vista tengo:

     
  • Home
  • ...
  • Usando una directiva (ya que estamos haciendo la manipulación de DOM aquí) la siguiente es probablemente la más cercana a hacer las cosas de la “manera angular”:

     $scope.timeFilters = [ {'value':3600,'label':'1 hour'}, {'value':10800,'label':'3 hours'}, {'value':21600,'label':'6 hours'}, {'value':43200,'label':'12 hours'}, {'value':86400,'label':'24 hours'}, {'value':604800,'label':'1 week'} ] angular.module('whatever', []).directive('filter',function(){ return{ restrict: 'A', template: '
  • {{time.label}}
  • ', link: function linkFn(scope, lElement, attrs){ var menuContext = attrs.filter; scope.changeTimeFilter = function(newTime){ scope.selectedtimefilter = newTime; } lElement.bind('click', function(cevent){ var currentSelection = angular.element(cevent.srcElement).parent(); var previousSelection = scope[menuContext]; if(previousSelection !== currentSelection){ if(previousSelection){ angular.element(previousSelection).removeClass('active') } scope[menuContext] = currentSelection; scope.$apply(function(){ currentSelection.addClass('active'); }) } }) } } })

    Entonces su HTML se vería así:

      

    Lo hice así:

     var myApp = angular.module('myApp', ['ngRoute']); myApp.directive('trackActive', function($location) { function link(scope, element, attrs){ scope.$watch(function() { return $location.path(); }, function(){ var links = element.find('a'); links.removeClass('active'); angular.forEach(links, function(value){ var a = angular.element(value); if (a.attr('href') == '#' + $location.path() ){ a.addClass('active'); } }); }); } return {link: link}; }); 

    Esto le permite tener enlaces en una sección que tiene directiva de seguimiento activo:

      

    Este enfoque parece mucho más limpio que otros, para mí.

    Además, si está utilizando jQuery, puede hacerlo mucho más ordenado porque jQlite solo tiene soporte de selector básico. Una versión mucho más limpia con jquery incluida antes de la inclusión angular se vería así:

     myApp.directive('trackActive', function($location) { function link(scope, element, attrs){ scope.$watch(function() { return $location.path(); }, function(){ element.find('a').removeClass('active').find('[href="#'+$location.path()+'"]').addClass('active'); }); } return {link: link}; }); 

    Aquí hay un jsFiddle

    Mi solución a este problema, use route.current en la plantilla angular.

    Como tiene la ruta https://stackoverflow.com/tasks para resaltar en su menú, puede agregar su propiedad menuItem a las rutas declaradas por su módulo:

     $routeProvider. when('https://stackoverflow.com/tasks', { menuItem: 'TASKS', templateUrl: 'my-templateshttps://stackoverflow.com/tasks.html', controller: 'TasksController' ); 

    Luego, en su plantilla tasks.html puede usar la siguiente directiva ng-class :

     Tasks 

    En mi opinión, esto es mucho más limpio que todas las soluciones propuestas.

    Aquí hay una extensión de la directiva kfis que hice para permitir diferentes niveles de coincidencia de ruta. Básicamente, encontré la necesidad de hacer coincidir las rutas de las URL hasta cierta profundidad, ya que la coincidencia exacta no permite el anidamiento y las redirecciones de estado predeterminadas. Espero que esto ayude.

      .directive('selectedLink', ['$location', function(location) { return { restrict: 'A', scope:{ selectedLink : '=' }, link: function(scope, element, attrs, controller) { var level = scope.selectedLink; 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) { var i=0; p = path.split('/'); n = newPath.split('/'); for( i ; i < p.length; i++) { if( p[i] == 'undefined' || n[i] == 'undefined' || (p[i] != n[i]) ) break; } if ( (i-1) >= level) { element.addClass("selected"); } else { element.removeClass("selected"); } }); } }; }]); 

    Y aquí es cómo uso el enlace

      

    Esta directiva coincidirá con el nivel de profundidad especificado en el valor del atributo para la directiva. Solo significa que puede usarse en otro lugar muchas veces.

    Aquí hay otra directiva para resaltar enlaces activos.

    Principales características:

    • Funciona bien con href que contiene expresiones angulares dinámicas
    • Compatible con la navegación hash-bang
    • Compatible con Bootstrap donde la clase activa se debe aplicar a parent li, no al enlace en sí
    • Permite activar el enlace si hay alguna ruta anidada activa
    • Permite deshabilitar el enlace si no está activo

    Código:

     .directive('activeLink', ['$location', function($location) { return { restrict: 'A', link: function(scope, elem, attrs) { var path = attrs.activeLink ? 'activeLink' : 'href'; var target = angular.isDefined(attrs.activeLinkParent) ? elem.parent() : elem; var disabled = angular.isDefined(attrs.activeLinkDisabled) ? true : false; var nested = angular.isDefined(attrs.activeLinkNested) ? true : false; function inPath(needle, haystack) { var current = (haystack == needle); if (nested) { current |= (haystack.indexOf(needle + '/') == 0); } return current; } function toggleClass(linkPath, locationPath) { // remove hash prefix and trailing slashes linkPath = linkPath ? linkPath.replace(/^#!/, '').replace(/\/+$/, '') : ''; locationPath = locationPath.replace(/\/+$/, ''); if (linkPath && inPath(linkPath, locationPath)) { target.addClass('active'); if (disabled) { target.removeClass('disabled'); } } else { target.removeClass('active'); if (disabled) { target.addClass('disabled'); } } } // watch if attribute value changes / evaluated attrs.$observe(path, function(linkPath) { toggleClass(linkPath, $location.path()); }); // watch if location changes scope.$watch( function() { return $location.path(); }, function(newPath) { toggleClass(attrs[path], newPath); } ); } }; } ]); 

    Uso:

    Ejemplo simple con expresión angular, digamos $ scope.var = 2 , luego el enlace estará activo si la ubicación es / url / 2 :

      

    Ejemplo de Bootstrap, parent li obtendrá clase activa:

     
  • Ejemplo con urls nesteds, el enlace estará activo si cualquier url anidada está activa (es decir, / url / 1 , / url / 2 , url / 1/2 / … )

      

    Ejemplo complejo, el enlace apunta a una url ( / url1 ) pero estará activo si se selecciona otra ( / url2 ):

      

    Ejemplo con enlace deshabilitado, si no está activo tendrá clase ‘deshabilitada’ :

      

    Todos los atributos de enlace activo * se pueden usar en cualquier combinación, por lo que se podrían implementar condiciones muy complejas.

    Si quiere los enlaces para la directiva en un contenedor en lugar de seleccionar cada enlace individual (hace que sea más fácil ver el scope en Batarang), esto funciona bastante bien también:

      angular.module("app").directive("navigation", [ "$location", function($location) { return { restrict: 'A', scope: {}, link: function(scope, element) { var classSelected, navLinks; scope.location = $location; classSelected = 'selected'; navLinks = element.find('a'); scope.$watch('location.path()', function(newPath) { var el; el = navLinks.filter('[href="' + newPath + '"]'); navLinks.not(el).closest('li').removeClass(classSelected); return el.closest('li').addClass(classSelected); }); } }; } ]); 

    El marcado sería:

       

    También debo mencionar que estoy usando jQuery ‘lleno de grasa’ en este ejemplo, pero puede modificar fácilmente lo que he hecho con el filtrado y demás.

    De acuerdo con la respuesta de @kfis, son los comentarios, y mi recomendación, la directiva final, como se muestra a continuación:

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

    y así es como se usaría en html:

      

    luego estilo con css:

     .active { color: red; } 

    Para aquellos que usan ui-router, mi respuesta es similar a la de Ender2050, pero prefiero hacerlo a través de la prueba de nombre de estado:

     $scope.isActive = function (stateName) { var active = (stateName === $state.current.name); return active; }; 

    HTML correspondiente:

      

    Ninguna de las sugerencias de directiva anteriores me resultó útil. Si tienes una barra de navegación de arranque como esta

      

    (podría ser un inicio $ yo angular ), entonces desea agregar .active a la lista de clases de elemento padre

  • , no al elemento en sí; es decir,

  • ..
  • . Entonces escribí esto:

     .directive('setParentActive', ['$location', function($location) { return { restrict: 'A', link: function(scope, element, attrs, controller) { var classActive = attrs.setParentActive || 'active', path = attrs.ngHref.replace('#', ''); scope.location = $location; scope.$watch('location.path()', function(newPath) { if (path == newPath) { element.parent().addClass(classActive); } else { element.parent().removeClass(classActive); } }) } } }]) 

    uso set-parent-active ; .active es predeterminado, por lo que no es necesario configurarlo

     
  • About
  • y el elemento padre

  • estará .active cuando el enlace esté activo. Para usar una clase .active alternativa como .highlight , simplemente

     
  • About
  • Lo más importante para mí fue no cambiar en absoluto el código predeterminado de arranque. Aquí es mi controlador de menú que busca opciones de menú y luego agrega el comportamiento que queremos.

     file: header.js function HeaderCtrl ($scope, $http, $location) { $scope.menuLinkList = []; defineFunctions($scope); addOnClickEventsToMenuOptions($scope, $location); } function defineFunctions ($scope) { $scope.menuOptionOnClickFunction = function () { for ( var index in $scope.menuLinkList) { var link = $scope.menuLinkList[index]; if (this.hash === link.hash) { link.parentElement.className = 'active'; } else { link.parentElement.className = ''; } } }; } function addOnClickEventsToMenuOptions ($scope, $location) { var liList = angular.element.find('li'); for ( var index in liList) { var liElement = liList[index]; var link = liElement.firstChild; link.onclick = $scope.menuOptionOnClickFunction; $scope.menuLinkList.push(link); var path = link.hash.replace("#", ""); if ($location.path() === path) { link.parentElement.className = 'active'; } } }   

    tenía el mismo problema Aquí está mi solución :

     .directive('whenActive', [ '$location', ($location)-> scope: true, link: (scope, element, attr)-> scope.$on '$routeChangeSuccess', () -> loc = "#"+$location.path() href = element.attr('href') state = href.indexOf(loc) substate = -1 if href.length > 3 substate = loc.indexOf(href) if loc.length is 2 state = -1 #console.log "Is Loc: "+loc+" in Href: "+href+" = "+state+" and Substate = "+substate if state isnt -1 or substate isnt -1 element.addClass 'selected' element.parent().addClass 'current-menu-item' else if href is '#' and loc is '#/' element.addClass 'selected' element.parent().addClass 'current-menu-item' else element.removeClass 'selected' element.parent().removeClass 'current-menu-item' ]) 

    Aquí están mis dos centavos, esto funciona bien.

    NOTA: Esto no coincide con las páginas secundarias (que es lo que necesitaba).

    Ver:

      Some link  

    Controlador:

     // make sure you inject $location as a dependency $scope.isCurrentLocation = function(path){ return path === $location.path() } 

    Acabo de escribir una directiva para esto.

    Uso:

      

    Implementación:

     angular.module('appName') .directive('active', function ($location, $timeout) { return { restrict: 'A', link: function (scope, element, attrs) { // Whenever the user navigates to a different page... scope.$on('$routeChangeSuccess', function () { // Defer for other directives to load first; this is important // so that in case other directives are used that this directive // depends on, such as ng-href, the href is evaluated before // it's checked here. $timeout(function () { // Find link inside li element var $link = element.children('a').first(); // Get current location var currentPath = $location.path(); // Get location the link is pointing to var linkPath = $link.attr('href').split('#').pop(); // If they are the same, it means the user is currently // on the same page the link would point to, so it should // be marked as such if (currentPath === linkPath) { $(element).addClass('active'); } else { // If they're not the same, a li element that is currently // marked as active needs to be "un-marked" element.removeClass('active'); } }); }); } }; }); 

    Pruebas:

     'use strict'; describe('Directive: active', function () { // load the directive's module beforeEach(module('appName')); var element, scope, location, compile, rootScope, timeout; beforeEach(inject(function ($rootScope, $location, $compile, $timeout) { scope = $rootScope.$new(); location = $location; compile = $compile; rootScope = $rootScope; timeout = $timeout; })); describe('with an active link', function () { beforeEach(function () { // Trigger location change location.path('/foo'); }); describe('href', function () { beforeEach(function () { // Create and compile element with directive; note that the link // is the same as the current location after the location change. element = angular.element('
  • Foo
  • '); element = compile(element)(scope); // Broadcast location change; the directive waits for this signal rootScope.$broadcast('$routeChangeSuccess'); // Flush timeout so we don't have to write asynchronous tests. // The directive defers any action using a timeout so that other // directives it might depend on, such as ng-href, are evaluated // beforehand. timeout.flush(); }); it('adds the class "active" to the li', function () { expect(element.hasClass('active')).toBeTruthy(); }); }); describe('ng-href', function () { beforeEach(function () { // Create and compile element with directive; note that the link // is the same as the current location after the location change; // however this time with an ng-href instead of an href. element = angular.element('
  • Foo
  • '); element = compile(element)(scope); // Broadcast location change; the directive waits for this signal rootScope.$broadcast('$routeChangeSuccess'); // Flush timeout so we don't have to write asynchronous tests. // The directive defers any action using a timeout so that other // directives it might depend on, such as ng-href, are evaluated // beforehand. timeout.flush(); }); it('also works with ng-href', function () { expect(element.hasClass('active')).toBeTruthy(); }); }); }); describe('with an inactive link', function () { beforeEach(function () { // Trigger location change location.path('/bar'); // Create and compile element with directive; note that the link // is the NOT same as the current location after the location change. element = angular.element('
  • Foo
  • '); element = compile(element)(scope); // Broadcast location change; the directive waits for this signal rootScope.$broadcast('$routeChangeSuccess'); // Flush timeout so we don't have to write asynchronous tests. // The directive defers any action using a timeout so that other // directives it might depend on, such as ng-href, are evaluated // beforehand. timeout.flush(); }); it('does not add the class "active" to the li', function () { expect(element.hasClass('active')).not.toBeTruthy(); }); }); describe('with a formerly active link', function () { beforeEach(function () { // Trigger location change location.path('/bar'); // Create and compile element with directive; note that the link // is the same as the current location after the location change. // Also not that the li element already has the class "active". // This is to make sure that a link that is active right now will // not be active anymore when the user navigates somewhere else. element = angular.element('
  • Foo
  • '); element = compile(element)(scope); // Broadcast location change; the directive waits for this signal rootScope.$broadcast('$routeChangeSuccess'); // Flush timeout so we don't have to write asynchronous tests. // The directive defers any action using a timeout so that other // directives it might depend on, such as ng-href, are evaluated // beforehand. timeout.flush(); }); it('removes the "active" class from the li', function () { expect(element.hasClass('active')).not.toBeTruthy(); }); }); });

    The route:

     $routeProvider.when('/Account/', { templateUrl: '/Home/Account', controller: 'HomeController' }); 

    The menu html:

     
  • El controlador:

     angular.module('Home').controller('HomeController', function ($scope, $http, $location) { $scope.url = $location.url().replace(/\//g, "").toLowerCase(); ... 

    The problem I found here is that the menu item is active only when the full page is loaded. When the partial view is loaded the menu doesn’t change. Somebody knows why it happens?

    Here is a much better way to do it

      function tasksController($scope, $location) { $scope.isActive = function (viewLocation) { return viewLocation === $location.path(); }; } 
     $scope.getClass = function (path) { return String(($location.absUrl().split('?')[0]).indexOf(path)) > -1 ? 'active' : '' } 
  • MY BOOKING
  • MY FLEET
  • ADD DRIVER
  • INVOICE
  • MY PROFILE
  • LOG OUT
  • I found the easiest solution. just to compare indexOf in HTML

    var myApp = angular.module(‘myApp’, []);

     myApp.run(function($rootScope) { $rootScope.$on("$locationChangeStart", function(event, next, current) { $rootScope.isCurrentPath = $location.path(); }); }); 
  • Help