Définir le style d'onglet actif avec AngularJS

j'ai des itinéraires établis à AngularJS comme celui-ci:

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

j'ai quelques liens sur la barre supérieure coiffée comme des onglets. Comment puis-je ajouter une classe 'active' à un onglet en fonction du modèle ou de l'url?

144
demandé sur Sergei Basharov 2012-09-06 12:29:17

18 réponses

une façon de résoudre ce problème sans avoir à se fier aux URLs est d'ajouter un attribut personnalisé à chaque partie de la configuration $routeProvider , comme ceci:

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

Expose $route dans votre contrôleur:

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

définit la classe active basée sur l'onglet actif courant:

<li ng-class="{active: $route.current.activetab == 'dashboard'}"></li>
<li ng-class="{active: $route.current.activetab == 'lab'}"></li>
273
répondu Rob Juurlink 2012-09-25 15:28:18

une façon d'y parvenir serait d'utiliser la directive ngClass et le service $location. Dans votre modèle, vous pouvez faire:

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

isActive serait une fonction dans une portée définie comme ceci:

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

voici le jsFiddle complet: http://jsfiddle.net/pkozlowski_opensource/KzAfG/

répéter ng-class="{active:isActive('/dashboard')}" sur chaque onglet de navigation peut être fastidieux (si vous avez beaucoup d'onglets) donc cette logique pourrait être un candidat pour une directive très simple.

133
répondu pkozlowski.opensource 2012-09-06 18:23:31

suivant les conseils de Pavel d'utiliser une directive personnalisée, voici une version qui ne nécessite pas d'ajout de charge utile à la routeConfig, est super déclarative, et peut être adaptée pour réagir à n'importe quel niveau du chemin, en changeant simplement le slice() auquel vous prêtez attention.

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: 
                <li class="nav_tab">
                  <a href="#/home" detect-active-tab="1">HOME</a>
                </li> 
            */

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

nous réalisons nos objectifs en écoutant l'événement $routeChangeSuccess , plutôt qu'en plaçant un $watch sur le chemin. Je travail en vertu de la conviction que cela signifie que les la logique devrait fonctionner moins souvent, car je pense regarde le feu sur chaque $digest cycle.

Invoquez-le en passant votre argument de niveau path sur la déclaration de directive. Ceci spécifie quel morceau de l'emplacement $courant.path () vous voulez faire correspondre votre attribut href avec.

<li class="nav_tab"><a href="#/home" detect-active-tab="1">HOME</a></li>

donc, si vos onglets doivent réagir au niveau de base du chemin, faites l'argument '1'. Donc, quand l'emplacement.path () est "/home", il va correspondre avec "#/home" dans le href . Si vous avez des onglets qui devraient réagir au deuxième niveau, au troisième ou au onzième du chemin, ajustez-les en conséquence. Ce découpage à partir de 1 ou plus va contourner le '#' infâme dans le href, qui vivra à l'index 0.

la seule exigence est que vous invoquiez sur un <a> , car l'élément suppose la présence d'un attribut href , qu'il comparera au chemin courant. Cependant, vous pourriez adapter assez facilement à lire/écrire un élément parent ou enfant, si vous avez préféré invoquer sur le <li> ou quelque chose. Je digère cela parce que vous pouvez le réutiliser dans de nombreux contextes en modifiant simplement l'argument pathLevel. Si la profondeur de lecture était supposée dans la logique, vous auriez besoin de plusieurs versions de la directive à utiliser avec plusieurs parties de la navigation.


MODIFIER 3/18/14: La solution n'était pas suffisamment généralisée, et activer si vous avez défini un arg pour la valeur de 'activeTab' qui renvoie undefined contre $location.path() et href de l'élément . Parce que: undefined === undefined . Mis à jour pour corriger cette condition.

en travaillant sur cela, j'ai réalisé qu'il devait y avoir une version que vous pouvez simplement déclarer sur un élément parent, avec une structure de modèle comme celle-ci:

<nav id="header_tabs" find-active-tab="1">
    <a href="#/home" class="nav_tab">HOME</a>
    <a href="#/finance" class="nav_tab">Finance</a>
    <a href="#/hr" class="nav_tab">Human Resources</a>
    <a href="#/quarterly" class="nav_tab">Quarterly</a>
</nav>

notez que cette version ne ressemble plus du tout au HTML Bootstrap. Mais, il est plus moderne et utilise moins éléments, de sorte que je suis partial. Cette version de la directive, ainsi que l'original, sont maintenant disponible sur GitHub comme un module de connexion que vous pouvez simplement déclarer comme une dépendance. Je serais heureux de les acheter, si quelqu'un les utilise.

aussi, si vous voulez une version compatible bootstrap qui inclut <li> 's, vous pouvez aller avec le angular-ui-Bootstrap Tabs module , qui je pense est sorti après cet original post, et qui est peut-être encore plus déclaratif que celui-ci. Il est moins concis pour les choses de base, mais vous fournit quelques options supplémentaires, comme les onglets désactivés et les événements déclaratifs qui tirent sur Activer et désactiver.

41
répondu XML 2014-03-29 00:33:18

@rob-juurlink j'ai amélioré un peu votre solution:

au lieu de chaque route nécessitant un onglet actif; et ayant besoin de régler l'onglet actif dans chaque contrôleur je fais ceci:

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

et le HTML ressemble à ceci. L'activetab est juste l'url qui se rapporte à cette route. Cela supprime simplement le besoin d'ajouter du code dans chaque controller (en faisant glisser des dépendances comme $route et $rootScope si c'est la seule raison pour laquelle ils sont utilisés)

<ul>
    <li ng-class="{active: activetab=='/dashboard'}">
       <a href="#/dashboard">dashboard</a>
    </li>
    <li ng-class="{active: activetab=='/lab'}">
       <a href="#/lab">lab</a>
    </li>
</ul>
27
répondu Lucas 2014-03-19 20:55:32

peut-être qu'une directive comme celle-ci pourrait résoudre votre problème: http://jsfiddle.net/p3ZMR/4 /

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>

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

    };

}]);
15
répondu kfis 2014-01-21 18:02:09

la solution la plus simple ici:

comment configurer bootstrap navbar active class avec Angular JS?

qui est:

utiliser ng-controller pour exécuter un seul controller en dehors de la vue ng:

<div class="collapse navbar-collapse" ng-controller="HeaderController">
    <ul class="nav navbar-nav">
        <li ng-class="{ active: isActive('/')}"><a href="/">Home</a></li>
        <li ng-class="{ active: isActive('/dogs')}"><a href="/dogs">Dogs</a></li>
        <li ng-class="{ active: isActive('/cats')}"><a href="/cats">Cats</a></li>
    </ul>
</div>
<div ng-view></div>

et inclure dans les contrôleurs.js:

function HeaderController($scope, $location) 
{ 
    $scope.isActive = function (viewLocation) { 
        return viewLocation === $location.path();
    };
}
14
répondu Zymotik 2017-05-23 11:47:27

je recommande d'utiliser l'état .module d'ui qui non seulement prennent en charge les vues multiples et imbriquées, mais aussi rendent ce genre de travail très facile (code ci-dessous Cité):

<ul class="nav">
    <li ng-class="{ active: $state.includes('contacts') }"><a href="#{{$state.href('contacts')}}">Contacts</a></li>
    <li ng-class="{ active: $state.includes('about') }"><a href="#{{$state.href('about')}}">About</a></li>
</ul>

à lire.

12
répondu David Lin 2013-08-01 00:39:10

Voici une autre version de la modification LI de xmlillies w/ domi qui utilise une chaîne de recherche au lieu d'un niveau de chemin. Je pense que c'est un peu plus évident ce qui se passe pour ma mallette.

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.
          // I.e. <li><a href="#/nested/section1/partial" activeTab="/section1">First Partial</a></li>
          if ($location.path().indexOf(attrs.activeTab) >= 0) {
            element.parent().addClass("active");//parent to get the <li>
          } else {
            element.parent().removeClass("active");
          }
        }
      });
    }
  };
});

HTML ressemble maintenant à:

<ul class="nav nav-tabs">
  <li><a href="#/news" active-tab="/news">News</a></li>
  <li><a href="#/some/nested/photos/rawr" active-tab="/photos">Photos</a></li>
  <li><a href="#/contact" active-tab="/contact">Contact</a></li>
</ul>
4
répondu Dave Rapin 2013-08-29 16:11:42

J'ai trouvé xmlilley's anwser le meilleur et le plus adaptable et non intrusif.

cependant j'ai eu un petit problème.

pour une utilisation avec bootstrap nav, voici comment je l'ai modifié:

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: <li class="nav_tab"><a href="#/home" 
                                   active-tab="1">HOME</a></li> 
            */
            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 <li>
                }
                else {
                  element.parent().removeClass("active");
                }
            }
        });
      }
    };
  });

j'ai ajouté " si (attrs.href!=Non défini) " parce que cette fonction est d'ailleurs appelée deux fois, la deuxième fois produisant une erreur.

comme pour le html:

<ul class="nav nav-tabs">
   <li class="active" active-tab="1"><a href="#/accueil" active-tab="1">Accueil</a></li>
   <li><a active-tab="1" href="#/news">News</a></li>
   <li><a active-tab="1" href="#/photos" >Photos</a></li>
   <li><a active-tab="1" href="#/contact">Contact</a></li>
</ul>
3
répondu domi 2013-08-15 13:09:52

Bootstrap example.

Si vous utilisez Angulars construit dans de routage (ngview) cette directive peut être utilisée:

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

        }
    };
}]);

en supposant que votre balisage ressemble à ceci:

    <ul class="nav navbar-nav">
        <li class-on-active-link="active"><a href="/orders">Orders</a></li>
        <li class-on-active-link="active"><a href="/distributors">Distributors</a></li>
    </ul>

j'aime ce fut de le faire puisque vous pouvez définir le nom de la classe que vous voulez dans votre attribut.

3
répondu Michael Falck Wedelgård 2015-05-13 05:37:18

vous pouvez aussi simplement injecter l'emplacement dans la portée et utiliser cela pour déduire le style pour la navigation:

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

puis utilisez-le dans votre ng-class :

<li ng-class="{active: location.path() == '/search'}">
  <a href="/search">Search><a/>
</li>
2
répondu Der Hochstapler 2013-02-01 14:50:23

une autre façon est d'utiliser ui-sref-active

une directive travaillant avec ui-sref pour ajouter des classes à un élément lorsque l'état de la directive apparentée ui-sref est actif, et les supprimer quand il est inactif. Le cas d'utilisation principal est de simplifier l'apparence spéciale des menus de navigation en s'appuyant sur ui-sref, en faisant apparaître le bouton menu de l'état "actif" différent, en le distinguant des éléments de menu inactifs.

Utilisation:

ui-sref - active='class1 class2 class3'-les classes "class1", "class2" et "class3" sont ajoutées à l'élément de la directive lorsque l'état de l'ui-sref est actif, puis supprimées lorsqu'il est inactif.

exemple:

Étant donné le modèle suivant,

<ul>
  <li ui-sref-active="active" class="item">
    <a href ui-sref="app.user({user: 'bilbobaggins'})">@bilbobaggins</a>
  </li>
  <!-- ... -->
</ul>

lorsque l'état app est " app.utilisateur", et contient le paramètre d'état "utilisateur" avec la valeur "bilbobaggins", le HTML qui en résulte apparaîtra comme

<ul>
  <li ui-sref-active="active" class="item active">
    <a ui-sref="app.user({user: 'bilbobaggins'})" href="/users/bilbobaggins">@bilbobaggins</a>
  </li>
  <!-- ... -->
</ul>

le nom de classe est interpolé une fois pendant le temps de lien des directives (toute autre modification de la valeur interpolée est ignorée). Les classes multiples peuvent être spécifiées dans un format séparé par des espaces.

utilisez la directive ui-sref-opts pour passer les options à $state.aller.)( Exemple:

<a ui-sref="home" ui-sref-opts="{reload: true}">Home</a>
2
répondu George Botros 2016-02-01 13:57:02

je suis d'accord avec le post de Rob sur le fait d'avoir un attribut personnalisé dans le contrôleur. Apparemment, je n'ai pas assez de relations pour commenter. Voici le jsfiddle qui a été demandé:

exemple html

<div ng-controller="MyCtrl">
    <ul>
        <li ng-repeat="link in links" ng-class="{active: $route.current.activeNav == link.type}"> <a href="{{link.uri}}">{{link.name}}</a>

        </li>
    </ul>
</div>

exemple d'applicationjs

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/

1
répondu jasontwong 2013-03-13 21:18:24
'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();
    };
  });

et utiliser ce qui suit dans le modèle:

<li role="presentation" ng-class="{active:isActive(menuItem.link)}" ng-repeat="menuItem in menu"><a href="{{menuItem.link}}"><i class="{{menuItem.iconClass}}"></i>&nbsp;&nbsp;{{menuItem.title}}</a></li>
1
répondu Yeshodhan Kulkarni 2015-06-03 23:04:21

j'avais besoin d'une solution qui ne nécessite pas de modifications aux controllers, parce que pour certaines pages nous ne faisons que des templates et il n'y a pas de controller du tout. Merci aux commentateurs précédents qui ont suggéré d'utiliser $routeChangeSuccess j'ai trouvé quelque chose comme ceci:

# 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

il ne dépend pas des URLs et il est donc très facile de le configurer pour toutes les sous-pages, etc.

0
répondu szimek 2013-10-25 14:02:50

Je ne me souviens pas où j'ai trouvé cette méthode, mais c'est assez simple et fonctionne bien.

HTML:

<nav role="navigation">
    <ul>
        <li ui-sref-active="selected" class="inactive"><a ui-sref="tab-01">Tab 01</a></li> 
        <li ui-sref-active="selected" class="inactive"><a ui-sref="tab-02">Tab 02</a></li>
    </ul>
</nav>

CSS:

  .selected {
    background-color: $white;
    color: $light-blue;
    text-decoration: none;
    border-color: $light-grey;
  } 
0
répondu cfranklin 2016-04-12 15:03:58

si vous utilisez ngRoute (pour l'acheminement) alors votre application aura une configuration inférieure,

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

Maintenant, il suffit d'ajouter un contrôleur dans cette configuration comme ci-dessous,

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

comme vous avez mentionné l'onglet actif dans votre configuration, vous n'avez plus qu'à ajouter la classe active dans votre étiquette <li> ou <a> . Comme,

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

ce qui signifie que, chaque fois que l'utilisateur clique sur la page à propos de identifier automatiquement l'onglet courant et appliquer la classe CSS active.

j'espère que cela aidera!

0
répondu imbond 2018-06-06 12:57:36

est venu ici pour la solution .. bien que les solutions ci-dessus fonctionnent bien, mais les a trouvées peu un peu complexe inutile. Pour les personnes qui cherchent encore une solution facile et soignée, il fera la tâche parfaitement.

<section ng-init="tab=1">
                <ul class="nav nav-tabs">
                    <li ng-class="{active: tab == 1}"><a ng-click="tab=1" href="#showitem">View Inventory</a></li>
                    <li ng-class="{active: tab == 2}"><a ng-click="tab=2" href="#additem">Add new item</a></li>
                    <li ng-class="{active: tab == 3}"><a ng-click="tab=3" href="#solditem">Sold item</a></li>
                </ul>
            </section>
-3
répondu MGA 2015-07-19 12:04:04