Establecer el estilo de tabs activas con AngularJS

Tengo rutas establecidas en AngularJS como esta:

$routeProvider .when('/dashboard', {templateUrl:'partials/dashboard', controller:widgetsController}) .when('/lab', {templateUrl:'partials/lab', controller:widgetsController}) 

Tengo algunos enlaces en la barra superior con el estilo de tabs. ¿Cómo puedo agregar una clase ‘activa’ a una pestaña según la plantilla o URL actual?

Una forma de resolver esto sin tener que depender de las URL es agregar un atributo personalizado a cada parcial durante la configuración $routeProvider , como esta:

 $routeProvider. when('/dashboard', { templateUrl: 'partials/dashboard.html', controller: widgetsController, activetab: 'dashboard' }). when('/lab', { templateUrl: 'partials/lab.html', controller: widgetsController, activetab: 'lab' }); 

Expone $route en tu controlador:

 function widgetsController($scope, $route) { $scope.$route = $route; } 

Establezca la clase active según la pestaña activa actual:

 
  • Una forma de hacerlo sería usando la directiva ngClass y el servicio $ location. En tu plantilla puedes hacer:

     ng-class="{active:isActive('/dashboard')}" 

    donde isActive sería una función en un ámbito definido así:

     myApp.controller('MyCtrl', function($scope, $location) { $scope.isActive = function(route) { return route === $location.path(); } }); 

    Aquí está el jsFiddle completo: http://jsfiddle.net/pkozlowski_opensource/KzAfG/

    Repetir ng-class="{active:isActive('/dashboard')}" en cada pestaña de navegación puede ser tedioso (si tiene muchas tabs), por lo que esta lógica podría ser candidata para una directiva muy simple.

    Siguiendo el consejo de Pavel de usar una directiva personalizada, aquí hay una versión que requiere que no se agregue carga útil a la routeConfig, es super declarativa y se puede adaptar para reactjsr a cualquier nivel de la ruta, simplemente cambiando qué slice() de ella prestar atención a.

     app.directive('detectActiveTab', function ($location) { return { link: function postLink(scope, element, attrs) { scope.$on("$routeChangeSuccess", function (event, current, previous) { /* Designed for full re-usability at any path, any level, by using data from attrs. Declare like this:  */ // This var grabs the tab-level off the attribute, or defaults to 1 var pathLevel = attrs.detectActiveTab || 1, // This var finds what the path is at the level specified pathToCheck = $location.path().split('/')[pathLevel] || "current $location.path doesn't reach this level", // This var finds grabs the same level of the href attribute tabLink = attrs.href.split('/')[pathLevel] || "href doesn't include this level"; // Above, we use the logical 'or' operator to provide a default value // in cases where 'undefined' would otherwise be returned. // This prevents cases where undefined===undefined, // possibly causing multiple tabs to be 'active'. // now compare the two: if (pathToCheck === tabLink) { element.addClass("active"); } else { element.removeClass("active"); } }); } }; }); 

    Estamos logrando nuestros objectives al escuchar el evento $routeChangeSuccess , en lugar de colocar un $watch en la ruta. Trabajo bajo la creencia de que esto significa que la lógica debe ejecutarse con menos frecuencia, ya que creo que los relojes disparan en cada $digest ciclo.

    Invocarlo pasando su argumento de nivel de ruta en la statement de directiva. Esto especifica qué porción del $ location.path actual () desea href su atributo href .

      

    Entonces, si sus tabs deben reactjsr al nivel base de la ruta, haga el argumento ‘1’. Por lo tanto, cuando location.path () es “/ home”, coincidirá con el “# / home” en el href . Si tiene tabs que deberían reactjsr al segundo nivel, o al tercero, u once de la ruta, ajuste en consecuencia. Este corte de 1 o mayor pasará por alto el nefasto ‘#’ en el href, que vivirá en el índice 0.

    El único requisito es invocar en un , ya que el elemento asume la presencia de un atributo href , que se comparará con la ruta actual. Sin embargo, puede adaptar bastante fácilmente para leer / escribir un elemento primario o secundario, si prefiere invocar en el

  • o algo así. Excavo esto porque puedes reutilizarlo en muchos contextos simplemente variando el argumento pathLevel. Si se supuso la profundidad de lectura en la lógica, necesitaría varias versiones de la directiva para usar con varias partes de la navegación.


    EDIT 18/3/14: La solución no se ha generalizado de forma adecuada y se activaría si definiera un argumento para el valor de ‘activeTab’ que devuelve undefined frente a $location.path() y al href del elemento. Porque: undefined === undefined . Actualizado para corregir esa condición.

    Mientras trabajaba en eso, me di cuenta de que debería haber una versión que se puede declarar en un elemento primario, con una estructura de plantilla como esta:

      

    Tenga en cuenta que esta versión ya no se parece remotamente al estilo Bootstrap HTML. Pero es más moderno y usa menos elementos, por lo que soy parcial. Esta versión de la directiva, más la original, ahora están disponibles en Github como un módulo adicional que puede declarar como una dependencia. Estaría feliz de Bower -ize, si alguien realmente los usa.

    Además, si quieres una versión compatible con bootstrap que incluya

  • , puedes ir con el módulo de tabs angular-ui-bootstrap , que creo que surgió después de esta publicación original, y que es quizás incluso más declarativo que este uno. Es menos conciso para cosas básicas, pero te brinda algunas opciones adicionales, como tabs deshabilitadas y eventos declarativos que activan y desactivan el encendido.

    @ rob-juurlink He mejorado un poco en su solución:

    en lugar de que cada ruta necesite una pestaña activa; y la necesidad de establecer la pestaña activa en cada controlador que hago esto:

     var App = angular.module('App',[]); App.config(['$routeProvider', function($routeProvider){ $routeProvider. when('/dashboard', { templateUrl: 'partials/dashboard.html', controller: Ctrl1 }). when('/lab', { templateUrl: 'partials/lab.html', controller: Ctrl2 }); }]).run(['$rootScope', '$location', function($rootScope, $location){ var path = function() { return $location.path();}; $rootScope.$watch(path, function(newVal, oldVal){ $rootScope.activetab = newVal; }); }]); 

    Y el HTML se ve así. La etiqueta activa es solo la URL que se relaciona con esa ruta. Esto simplemente elimina la necesidad de agregar código en cada controlador (arrastrando dependencias como $ route y $ rootScope si esta es la única razón por la que se usan)

      

    Tal vez una directiva como esta pueda resolver su problema: http://jsfiddle.net/p3ZMR/4/

    HTML

      

    JS

     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 bot return including hashbang scope.location = location; scope.$watch('location.path()', function(newPath) { if (path === newPath) { element.addClass(clazz); } else { element.removeClass(clazz); } }); } }; }]); 

    La solución más simple aquí:

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

    Cual es:

    Use ng-controller para ejecutar un único controlador fuera de la vista ng:

      

    e incluir en controllers.js:

     function HeaderController($scope, $location) { $scope.isActive = function (viewLocation) { return viewLocation === $location.path(); }; } 

    Recomiendo usar el módulo state.ui, que no solo es compatible con vistas múltiples y anidadas, sino que también hace que este tipo de trabajo sea muy fácil (código a continuación citado):

      

    Vale la pena leer.

    Aquí hay otra versión del cambio LI de XMLillies w / domi que usa una cadena de búsqueda en lugar de un nivel de ruta. Creo que esto es un poco más obvio lo que está sucediendo para mi caso de uso.

     statsApp.directive('activeTab', function ($location) { return { link: function postLink(scope, element, attrs) { scope.$on("$routeChangeSuccess", function (event, current, previous) { if (attrs.href!=undefined) { // this directive is called twice for some reason // The activeTab attribute should contain a path search string to match on. // Ie 
  • First Partial
  • if ($location.path().indexOf(attrs.activeTab) >= 0) { element.parent().addClass("active");//parent to get the
  • } else { element.parent().removeClass("active"); } } }); } }; });
  • El HTML ahora se ve así:

      

    Encontré el guante de XMLilley el mejor y más adaptable y no intrusivo.

    Sin embargo, tuve un pequeño problema.

    Para usar con el navegador bootstrap, así es como lo modifiqué:

     app.directive('activeTab', function ($location) { return { link: function postLink(scope, element, attrs) { scope.$on("$routeChangeSuccess", function (event, current, previous) { /* designed for full re-usability at any path, any level, by using data from attrs declare like this:  */ if(attrs.href!=undefined){// this directive is called twice for some reason // this var grabs the tab-level off the attribute, or defaults to 1 var pathLevel = attrs.activeTab || 1, // this var finds what the path is at the level specified pathToCheck = $location.path().split('/')[pathLevel], // this var finds grabs the same level of the href attribute tabLink = attrs.href.split('/')[pathLevel]; // now compare the two: if (pathToCheck === tabLink) { element.parent().addClass("active");//parent to get the 
  • } else { element.parent().removeClass("active"); } } }); } }; });
  • Agregué “if (attrs.href! = Undefined)” porque esta función se llama dos veces, y la segunda produce un error.

    En cuanto al html:

      

    Ejemplo de Bootstrap.

    Si está utilizando Angulares integrados en el enrutamiento (ngview), esta directiva se puede usar:

     angular.module('myApp').directive('classOnActiveLink', [function() { return { link: function(scope, element, attrs) { var anchorLink = element.children()[0].getAttribute('ng-href') || element.children()[0].getAttribute('href'); anchorLink = anchorLink.replace(/^#/, ''); scope.$on("$routeChangeSuccess", function (event, current) { if (current.$$route.originalPath == anchorLink) { element.addClass(attrs.classOnActiveLink); } else { element.removeClass(attrs.classOnActiveLink); } }); } }; }]); 

    Suponiendo que su marcado se vea así:

       

    Me gusta esto fue hacerlo ya que puede establecer el nombre de clase que desea en su atributo.

    También puede simplemente insertar la ubicación en el scope y usar eso para deducir el estilo de la navegación:

     function IndexController( $scope, $rootScope, $location ) { $rootScope.location = $location; ... } 

    Entonces úsala en tu ng-class :

     
  • Search>
  • una forma alternativa es usar ui-sref-active

    Una directiva que funciona junto con ui-sref para agregar clases a un elemento cuando el estado de la directiva ui-sref relacionada está activo y eliminarlas cuando está inactivo. El caso de uso principal es simplificar la apariencia especial de los menús de navegación que dependen de ui-sref, haciendo que el botón de menú del estado “activo” aparezca diferente, distinguiéndolo de los elementos del menú inactivo.

    Uso:

    ui-sref-active = ‘class1 class2 class3’ – las clases “class1”, “class2” y “class3” se agregan al elemento directivo cuando el estado del ui-sref relacionado está activo y se elimina cuando está inactivo.

    Ejemplo:
    Dada la siguiente plantilla,

      

    cuando el estado de la aplicación es “app.user”, y contiene el parámetro de estado “user” con el valor “bilbobaggins”, el HTML resultante aparecerá como

      

    El nombre de la clase se interpola una vez durante el tiempo de enlace de las directivas (cualquier cambio adicional al valor interpolado se ignora). Se pueden especificar múltiples clases en un formato separado del espacio.

    Use la directiva ui-sref-opts para pasar opciones a $ state.go (). Ejemplo:

     Home 

    Estoy de acuerdo con la publicación de Rob sobre tener un atributo personalizado en el controlador. Aparentemente no tengo suficiente representante para comentar. Aquí está el jsfiddle que se solicitó:

    muestra html

      

    ejemplo app.js

     angular.module('MyApp', []).config(['$routeProvider', function ($routeProvider) { $routeProvider.when('/a', { activeNav: 'a' }) .when('/a/:id', { activeNav: 'a' }) .when('/b', { activeNav: 'b' }) .when('/c', { activeNav: 'c' }); }]) .controller('MyCtrl', function ($scope, $route) { $scope.$route = $route; $scope.links = [{ uri: '#/a', name: 'A', type: 'a' }, { uri: '#/b', name: 'B', type: 'b' }, { uri: '#/c', name: 'C', type: 'c' }, { uri: '#/a/detail', name: 'A Detail', type: 'a' }]; }); 

    http://jsfiddle.net/HrdR6/

     'use strict'; angular.module('cloudApp') .controller('MenuController', function ($scope, $location, CloudAuth) { $scope.menu = [ { 'title': 'Dashboard', 'iconClass': 'fa fa-dashboard', 'link': '/dashboard', 'active': true }, { 'title': 'Devices', 'iconClass': 'fa fa-star', 'link': '/devices' }, { 'title': 'Settings', 'iconClass': 'fa fa-gears', 'link': '/settings' } ]; $location.path('/dashboard'); $scope.isLoggedIn = CloudAuth.isLoggedIn; $scope.isAdmin = CloudAuth.isAdmin; $scope.isActive = function(route) { return route === $location.path(); }; }); 

    Y usa lo siguiente en la plantilla:

     
  •   {{menuItem.title}}
  • Necesitaba una solución que no requiera cambios en los controladores, porque para algunas páginas solo renderizamos plantillas y no hay ningún controlador. Gracias a los comentaristas anteriores que sugirieron usar $routeChangeSuccess me ocurrió algo como esto:

     # Directive angular.module('myapp.directives') .directive 'ActiveTab', ($route) -> restrict: 'A' link: (scope, element, attrs) -> klass = "active" if $route.current.activeTab? and attrs.flActiveLink is $route.current.activeTab element.addClass(klass) scope.$on '$routeChangeSuccess', (event, current) -> if current.activeTab? and attrs.flActiveLink is current.activeTab element.addClass(klass) else element.removeClass(klass) # Routing $routeProvider .when "/page", templateUrl: "page.html" activeTab: "page" .when "/other_page", templateUrl: "other_page.html" controller: "OtherPageCtrl" activeTab: "other_page" # View (.jade) a(ng-href='/page', active-tab='page') Page a(ng-href='/other_page', active-tab='other_page') Other page 

    No depende de las URL y, por lo tanto, es muy fácil configurarlo para cualquier subpágina, etc.

    No recuerdo dónde encontré este método, pero es bastante simple y funciona bien.

    HTML:

      

    CSS:

      .selected { background-color: $white; color: $light-blue; text-decoration: none; border-color: $light-grey; } 

    Si está utilizando ngRoute (para el enrutamiento), entonces su aplicación tendrá la configuración siguiente,

     angular .module('appApp', [ 'ngRoute' ]) config(function ($routeProvider) { $routeProvider .when('/', { templateUrl: 'views/main.html', controller: 'MainCtrl', controllerAs: 'main' }) .when('/about', { templateUrl: 'views/about.html', controller: 'AboutCtrl', controllerAs: 'about' }) } }); 

    Ahora, simplemente agregue un controlador en esta configuración al igual que a continuación,

     angular .module('appApp', [ 'ngRoute' ]) config(function ($routeProvider) { $routeProvider .when('/', { templateUrl: 'views/main.html', controller: 'MainCtrl', activetab: 'main' }) .when('/about', { templateUrl: 'views/about.html', controller: 'AboutCtrl', activetab: 'about' }) } }) .controller('navController', function ($scope, $route) { $scope.$route = $route; }); 

    Como mencionó la pestaña activa en su configuración, ahora solo tiene que agregar una clase activa en su etiqueta

  • o . Me gusta,

     ng-class="{active: $route.current.activetab == 'about'}" 

    Lo que significa que cada vez que el usuario haga clic en una página, identificará automáticamente la pestaña actual y aplicará la clase CSS activa.

    ¡Espero que esto ayude!

    Vine aquí por la solución … aunque las soluciones anteriores funcionan bien, pero me parecieron un poco complejas. Para las personas que aún buscan una solución fácil y ordenada, harán la tarea perfectamente.