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

205

¿AngularJS ayuda de alguna manera a establecer una activeclase en el enlace de la página actual?

Me imagino que hay una forma mágica de hacer esto, pero parece que no puedo encontrarlo.

Mi menú se ve así:

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

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

Pero no puedo encontrar una manera de vincular la clase "activa" en los aenlaces a los controladores.

¿Alguna pista?

Andriy Drozdyuk
fuente

Respuestas:

265

a la vista

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

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 '/ tareas' (por ejemplo, '/ tareas / 1 / informes')

Renan Tomal Fernandes
fuente
44
Esto terminaría haciendo coincidir tanto "/" como "/ cualquier cosa" o si tiene varios elementos de menú con URL similares, como "/ test", "/ test / this", "/ test / this / path" si estuviera en / test, destacaría todas esas opciones.
Ben Lesh
3
He cambiado esto a if ($ location.path () == ruta) y, y ruta es "/ blah", etc.
Tim
113
Prefiero la notación ngClass="{active: isActive('/tasks')}, donde isActive()devolvería un valor booleano ya que desacopla el controlador y el marcado / estilo.
Ed Hinchliffe
66
En caso de que alguien se preguntara sobre el código para no duplicar si la ruta es "/", esto es todo (perdón por el formato): $ scope.getClass = function (ruta) {if ($ location.path (). substr (0, path.length) == ruta) {if (ruta == "/" && $ location.path () == "/") {return "active"; } else if (ruta == "/") {return ""; } return "active"} else {return ""}}
1
EdHinchliffe ya señaló que esto combina marcado y lógica. También conduce a la duplicación de la ruta y, por lo tanto, puede ser propenso a copiar y pegar errores. Descubrí que el enfoque directivo de @kfis, aunque tiene más líneas, es más reutilizable y mantiene el marcado más limpio.
A. Murray
86

Sugiero usar una directiva en un enlace.

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

Aquí está la directiva JavaScript para:

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:

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

luego peinado con css:

.active { color: red; }
kfis
fuente
No estoy seguro de lo que quieres decir con "cuidado con los hashbangs". Parece que siempre funcionaría. ¿Podría proporcionar un contraejemplo?
Andriy Drozdyuk
77
Si está tratando de usar Bootstrap y necesita establecerlo en función del hash de un href dentro de un li, entonces úselo var path = $(element).children("a")[0].hash.substring(1);. Esto funcionará para un estilo como<li active-link="active"><a href="#/dashboard">Dashboard</a></li>
Dave
2
Que cambiaría scope.$watch('location.path()', function(newPath) {para scope.$on('$locationChangeStart', function(){.
sanfilippopablo
2
Si está usando ng-href, simplemente cambie: var path = attrs.href; a var path = attrs.href||attrs.ngHref;
William Neely
Si usa Bootstrap y necesita poner la clase activa en el <li>, puede cambiar element.addClass(clazz);aelement.parent().addClass(clazz);
JamesRLamar
47

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

<ul>
    <li ng-class="{ active: isActive('/View1') }"><a href="#/View1">View 1</a></li>
    <li ng-class="{ active: isActive('/View2') }"><a href="#/View2">View 2</a></li>
    <li ng-class="{ active: isActive('/View3') }"><a href="#/View3">View 3</a></li>
</ul>

Dentro de su controlador AngularJS:

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

Este hilo tiene otras respuestas similares.

¿Cómo configurar la clase activa bootstrap navbar con Angular JS?

Ender2050
fuente
1
Elimine la variable ya que es innecesaria. Simplemente devuelva el resultado de la comparación. return viewLocation === $location.path()
afarazit
33

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

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

<ul auto-active>
    <li><a href="#/">main</a></li>
    <li><a href="#/first">first</a></li>
    <li><a href="#/second">second</a></li>
    <li><a href="#/third">third</a></li>
</ul>

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, solo puede usar la parte directiva)

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

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

Pylinux
fuente
1
Gran solución, sin embargo, no funcionó en la carga de la página inicial, solo en locationChange mientras la aplicación está activa. Actualicé tu fragmento para manejar eso.
Jerry
@Jarek: ¡Gracias! Han implementado sus cambios. No he tenido problemas con esto personalmente, pero su solución parece una buena solución estable para aquellos que deberían tener este problema.
Pylinux
2
Ahora he creado un repositorio de Github para solicitudes de extracción si alguien más tiene buenas ideas: github.com/Karl-Gustav/autoActive
Pylinux
Acabo de corregir algunos errores más que ocurrirían si estuvieras usando ng-href .. Esto se encuentra aquí: github.com/Karl-Gustav/autoActive/pull/3
Blake Niemyjski
Sería bueno si este script le permitiera especificar una ruta para que pueda activar otros elementos.
Blake Niemyjski
22

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í:

<ul class="nav" ng-controller="NavigationCtrl">
  <li ng-class="{ active: isCurrentPath('/') }"><a href="#/">Home</a></li>
  <li ng-class="{ active: isCurrentPath('/about') }"><a href="#/about">About</a></li>
  <li ng-class="{ active: isCurrentPath('/contact') }"><a href="#/contact">Contact</a></li>
</ul>
Djamel
fuente
14

Para usuarios de enrutador AngularUI :

<a ui-sref-active="active" ui-sref="app">

Y eso colocará una activeclase en el objeto seleccionado.

frankie4fingers
fuente
2
Esta es una directiva de enrutador ui y no funciona si usa el enrutador incorporado, también conocido como ngRoute. Dicho esto, ui-router es increíble.
moljac024
De acuerdo, olvidé mencionar originalmente que era una solución única para enrutador ui.
frankie4fingers
13

Hay una ng-classdirectiva, que une la variable y la clase css. También acepta el objeto (className vs pares de valores bool).

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

Tosh
fuente
Gracias, pero esto no funcionará con rutas como: ¿ /test1/blahblaho sí?
Andriy Drozdyuk
Entonces, ¿estás diciendo que active: activePath=='/test1'automáticamente devuelve un "activo" si la ruta comienza con la cadena dada? ¿Es este un tipo de operador predefinido o regex?
Andriy Drozdyuk
Lo siento, no creo haber entendido su requisito correctamente. Aquí está mi nueva suposición, desea resaltar tanto el enlace 'prueba1' como el enlace 'prueba1 / blahblah' cuando la ruta es 'prueba1 / blahblah'. "¿Estoy en lo correcto? Si este es el requisito, tiene razón en que mi solución no trabajar.
Tosh
3
Aquí está el plnkr actualizado: plnkr.co/edit/JI5DtK (que satisface el requisito adivinado) para mostrar una solución alternativa.
Tosh
Veo lo que hiciste alli. Pero no soy fanático de los ==controles repetidos en el html.
Andriy Drozdyuk
13

La respuesta de @ Renan-tomal-fernandes es buena, pero necesitaba un par de mejoras para funcionar correctamente. Tal como estaba, siempre detectaría el enlace a la página de inicio (/) como activado, 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 <li>elemento en lugar de la <a>.

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

<div class="nav-collapse collapse">
  <ul class="nav">
    <li ng-class="getClass('/')"><a href="#/">Home</a></li>
    <li ng-class="getClass('/contents/')"><a href="#/contests/">Contents</a></li>
    <li ng-class="getClass('/data/')"><a href="#/data/">Your data</a></li>
  </ul>
</div>
holographix
fuente
10

Aquí está la solución que se me ocurrió después de leer algunas de las excelentes sugerencias anteriores. En mi situación particular, estaba tratando de usar el componente de pestañas Bootstrap como mi menú, pero no quería usar la versión Angular-UI de esto porque quiero que las pestañas actúen como un menú, donde cada pestaña puede agregar marcadores, en lugar de que las pestañas actúen como navegación para una sola página. (Consulte http://angular-ui.github.io/bootstrap/#/tabs si está interesado en cómo se ve la versión Angular-UI de las pestañas bootstrap).

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é en publicarlo 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 a JavaScript, pero crea un componente reutilizable con un marcado mínimo.

Aquí está el javascript para la directiva y el proveedor de ruta 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 su html, simplemente:

<ul nav-tabs>
  <li><a href="#/One">One</a></li>
  <li><a href="#/Two">Two</a></li>
  <li><a href="#/Three">Three</a></li>
</ul>
<ng:view><!-- Content will appear here --></ng:view>

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

corinnaerin
fuente
9

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

<div ng-controller="MenuCtrl">
  <ul class="menu">
    <li ng-class="menuClass('home')"><a href="#home">Page1</a></li>
    <li ng-class="menuClass('about')"><a href="#about">Page2</a></li>
  </ul>

</div>

Y su controlador debería ser este:

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

Tuve un problema similar con el menú ubicado fuera del alcance del controlador. No estoy seguro de si esta es la mejor solución o una recomendada, pero esto es lo que funcionó para mí. He agregado 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:

<li><a href="/" ng-class="{true: 'active'}[menuActive('/', true)]">Home</a></li>
<li><a href="/register" ng-class="{true: 'active'}[menuActive('/register')]">
<li>...</li>
mrt
fuente
Um ... esto parece más complicado que la respuesta aceptada. ¿Podría describir la ventaja de esto sobre ese?
Andriy Drozdyuk
1
Lo necesitará en el escenario cuando su menú esté fuera de ng-view. El controlador de vista no tendrá acceso a nada que esté afuera, así que he usado $ rootScope para habilitar la comunicación. Si su menú está dentro de la vista ng, entonces no tiene ningún beneficio usar esta solución.
MRT
4

Uso de la versión angular 6 con Bootstrap 4.1

Pude hacerlo como se ve a continuación.

En el ejemplo a continuación, cuando la URL ve '/ contact', el bootstrap activo se agrega a la etiqueta html. Cuando la URL cambia, se elimina.

<ul>
<li class="nav-item" routerLink="/contact" routerLinkActive="active">
    <a class="nav-link" href="/contact">Contact</a>
</li>
</ul>

Esta directiva le permite agregar una clase CSS a un elemento cuando la ruta del enlace se activa.

Lea más en el sitio web de Angular

Jeacovy Gayle
fuente
3

Usando una directiva (ya que estamos haciendo manipulación DOM aquí), lo siguiente es probablemente lo más cercano a hacer las cosas de la "forma 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: '<li ng-repeat="time in timeFilters" class="filterItem"><a ng-click="changeTimeFilter(time)">{{time.label}}</a></li>',
    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í:

<ul class="dropdown-menu" filter="times"></ul>
Wesley Hales
fuente
Interesante. Pero menu-itemparece redundante en cada línea. Quizás sea mejor adjuntar un atributo al ulelemento (por ejemplo <ul menu>), pero no estoy seguro de si eso sería posible.
Andriy Drozdyuk
Recién actualizado con una versión más nueva: en lugar de una lista estática desordenada, ahora estoy usando el menú desplegable Boostrap como una lista de selección.
Wesley Hales
Esto parece más anguloso idiomático. Parece encajar con los consejos dados en stackoverflow.com/questions/14994391/… , y evita duplicar la ruta en la vista, en la href y en la clase ng.
fundead
2

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 una directiva de seguimiento activo:

<nav track-active>
     <a href="#/">Page 1</a>
     <a href="#/page2">Page 2</a>
     <a href="#/page3">Page 3</a>
</nav>

Este enfoque me parece mucho más limpio que otros.

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 incluir 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

konsumer
fuente
2

Mi solución a este problema, uso route.currenten la plantilla angular.

Como tiene la /tasksruta para resaltar en su menú, puede agregar su propia propiedad menuItema las rutas declaradas por su módulo:

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

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

<a href="app.html#/tasks" 
    ng-class="{active : route.current.menuItem === 'TASKS'}">Tasks</a>

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

François Maturel
fuente
1

Aquí hay una extensión de la directiva kfis que hice para permitir diferentes niveles de coincidencia de ruta. Esencialmente encontré la necesidad de hacer coincidir las rutas de URL hasta una 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 así es como uso el enlace

<nav>
    <a href="#/info/project/list"  selected-link="2">Project</a>
    <a href="#/info/company/list" selected-link="2">Company</a>
    <a href="#/info/person/list"  selected-link="2">Person</a>
</nav>

Esta directiva coincidirá con el nivel de profundidad especificado en el valor del atributo para la directiva. Solo significa que se puede usar en otros lugares muchas veces.

pkbyron
fuente
1

Aquí hay otra directiva para resaltar los enlaces activos.

Características clave:

  • Funciona bien con href que contiene expresiones angulares dinámicas
  • Compatible con navegación hash-bang
  • Compatible con Bootstrap, donde la clase activa debe aplicarse a los padres, no al enlace en sí
  • Permite activar el enlace si alguna ruta anidada está activa
  • Permite desactivar 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 :

<a href="#!/url/{{var}}" active-link>

Ejemplo de Bootstrap, el padre li obtendrá una clase activa:

<li>
    <a href="#!/url" active-link active-link-parent>
</li>

Ejemplo con URL anidadas, el enlace estará activo si alguna url anidada está activa (es decir, / url / 1 , / url / 2 , url / 1/2 / ... )

<a href="#!/url" active-link active-link-nested>

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

<a href="#!/url1" active-link="#!/url2" active-link-nested>

Ejemplo con enlace deshabilitado, si no está activo tendrá la clase 'deshabilitado' :

<a href="#!/url" active-link active-link-disabled>

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

Eugene Fidelin
fuente
1

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

  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 solo sería:

    <nav role="navigation" data-navigation>
        <ul>
            <li><a href="/messages">Messages</a></li>
            <li><a href="/help">Help</a></li>
            <li><a href="/details">Details</a></li>
        </ul>
    </nav>

También debería mencionar que estoy usando jQuery 'lleno de grasa' en este ejemplo, pero puede modificar fácilmente lo que he hecho con el filtrado, etc.

marksyzm
fuente
1

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

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

Ver:

<a ng-class="{active: isCurrentLocation('/my-path')}"  href="/my-path" >
  Some link
</a>

Controlador:

// make sure you inject $location as a dependency

$scope.isCurrentLocation = function(path){
    return path === $location.path()
}
Justus Romijn
fuente
1

De acuerdo con la respuesta de @kfis, se trata de comentarios, y mi recomendación, la directiva final de la siguiente manera:

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

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

luego peinado con css:

.active { color: red; }
John_J
fuente
1

Para aquellos que usan enrutador ui, mi respuesta es algo similar a la de Ender2050, pero prefiero hacer esto a través de pruebas de nombre de estado:

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

HTML correspondiente:

<ul class="nav nav-sidebar">
    <li ng-class="{ active: isActive('app.home') }"><a ui-sref="app.home">Dashboard</a></li>
    <li ng-class="{ active: isActive('app.tiles') }"><a ui-sref="app.tiles">Tiles</a></li>
</ul>
GONeale
fuente
1

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

<ul class="nav navbar-nav">
    <li><a ng-href="#/">Home</a></li>
    <li><a ng-href="#/about">About</a></li>
  ...
</ul>

(que podría ser un $ yo angularinicio), entonces desea agregar .activea la lista de clases de elementos primarios <li> , no el elemento en sí; es decir <li class="active">..</li>. 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; .activees predeterminado, por lo que no es necesario configurarlo

<li><a ng-href="#/about" set-parent-active>About</a></li>

y el padre <li> elemento estará .activecuando el enlace esté activo. Para usar una .activeclase alternativa como .highlight, simplemente

<li><a ng-href="#/about" set-parent-active="highlight">About</a></li>
davidkonrad
fuente
Había intentado el alcance. $ On ("$ routeChangeSuccess", function (event, current, previous) {applyActiveClass ();}); pero solo funciona cuando se hace clic en el enlace y no "en la carga de la página" (haciendo clic en el botón Actualizar). Ver la ubicación me ha funcionado
Sawe
0

Lo más importante para mí fue no cambiar en absoluto el código predeterminado de arranque. Aquí es mi controlador de menú el que busca las 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';
    }
  }
}

     <script src="resources/js/app/header.js"></script>
 <div class="navbar navbar-fixed-top" ng:controller="HeaderCtrl">
    <div class="navbar-inner">
      <div class="container-fluid">
        <button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
          <span class="icon-bar"></span> <span class="icon-bar"></span> 
<span     class="icon-bar"></span>
        </button>
        <a class="brand" href="#"> <img src="resources/img/fom-logo.png"
          style="width: 80px; height: auto;">
        </a>
        <div class="nav-collapse collapse">
          <ul class="nav">
            <li><a href="#/platforms">PLATFORMS</a></li>
            <li><a href="#/functionaltests">FUNCTIONAL TESTS</a></li>
          </ul> 
        </div>
      </div>
    </div>
  </div>
usuario2599258
fuente
0

Tuve 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'
  ])
Naxmeify
fuente
0

Acabo de escribir una directiva para esto.

Uso:

<ul class="nav navbar-nav">
  <li active><a href="#/link1">Link 1</a></li>
  <li active><a href="#/link2">Link 2</a></li>
</ul>

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('<li active><a href="#/foo">Foo</a></li>');
        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('<li active><a ng-href="#/foo">Foo</a></li>');
        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('<li active><a href="#/foo">Foo</a></li>');
      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('<li class="active" active><a href="#/foo">Foo</a></li>');
      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();
    });
  });
});
Weltschmerz
fuente
0

La ruta:

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

El menú html:

<li id="liInicio" ng-class="{'active':url=='account'}">

El controlador:

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

El problema que encontré aquí es que el elemento del menú está activo solo cuando se carga la página completa. Cuando se carga la vista parcial, el menú no cambia. Alguien sabe por qué sucede?

Mr. D MX
fuente
0
$scope.getClass = function (path) {
return String(($location.absUrl().split('?')[0]).indexOf(path)) > -1 ? 'active' : ''
}


<li class="listing-head" ng-class="getClass('/v/bookings')"><a href="/v/bookings">MY BOOKING</a></li>
<li class="listing-head" ng-class="getClass('/v/fleets')"><a href="/v/fleets">MY FLEET</a></li>
<li class="listing-head" ng-class="getClass('/v/adddriver')"><a href="/v/adddriver">ADD DRIVER</a></li>
<li class="listing-head" ng-class="getClass('/v/bookings')"><a href="/v/invoice">INVOICE</a></li>
<li class="listing-head" ng-class="getClass('/v/profile')"><a href="/v/profile">MY PROFILE</a></li>
<li class="listing-head"><a href="/v/logout">LOG OUT</a></li>
Ashish Gupta
fuente
0

Encontré la solución más fácil. solo para comparar indexOf en HTML

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

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



<li class="{{isCurrentPath.indexOf('help')>-1 ? 'active' : '' }}">
<a href="/#/help/">
          Help
        </a>
</li>
NishantVerma.Me
fuente