web-dev-qa-db-fra.com

Définir le style de l'onglet actif avec AngularJS

J'ai des itinéraires dans AngularJS comme ceci:

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

J'ai des liens sur la barre supérieure sous la forme d'onglets. Comment puis-je ajouter une classe 'active' à un onglet en fonction du modèle ou de l'URL en cours?

144
Sergei Basharov

Un moyen de résoudre ce problème sans avoir à utiliser d'URL consiste à ajouter un attribut personnalisé à chaque partiel lors de la configuration de $routeProvider, comme ceci:

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

Exposez $route dans votre contrôleur:

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

Définissez la classe active en fonction de l'onglet actif en cours:

<li ng-class="{active: $route.current.activetab == 'dashboard'}"></li>
<li ng-class="{active: $route.current.activetab == 'lab'}"></li>
274
Rob Juurlink

Une façon de procéder consiste à 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 étendue définie comme ceci:

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

Voici l'intégralité de jsFiddle: 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 plusieurs onglets), alors cette logique peut être candidate à une directive très simple.

134

Suivant les conseils de Pavel concernant l’utilisation d’une directive personnalisée, voici une version qui n’ajoute aucune charge utile à routeConfig. Elle est super-déclarative et peut être adaptée pour réagir à n’importe quel niveau du chemin, en modifiant simplement la slice() de celle-ci. prêter 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 atteignons nos objectifs en écoutant l'événement $routeChangeSuccess plutôt qu'en plaçant un $watch sur le chemin. Je suis persuadé que cela signifie que la logique devrait être moins fréquente, car je pense que chaque cycle $digest est déclenché.

Invoquez-le en passant votre argument de niveau chemin dans la déclaration de directive. Ceci spécifie le bloc de la variable $ location.path () actuelle pour laquelle vous souhaitez faire correspondre votre attribut href.

<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, définissez l'argument '1'. Ainsi, lorsque location.path () est "/ home", il correspond au "#/home" dans le href. Si vous avez des onglets devant réagir au deuxième, au troisième ou au onzième du chemin, ajustez-le en conséquence. Ce découpage à partir de 1 ou plus contournera le '#' néfaste dans le href, qui vivra à l'indice 0.

La seule condition requise est que vous appeliez sur un <a>, car l'élément suppose la présence d'un attribut href, qu'il comparera au chemin actuel. Cependant, vous pourriez vous adapter assez facilement pour lire/écrire un élément parent ou enfant si vous préfériez appeler sur le <li> ou quelque chose du genre. Je creuse cela parce que vous pouvez le réutiliser dans de nombreux contextes en faisant simplement varier l'argument pathLevel. Si la profondeur à lire était supposée dans la logique, vous auriez besoin de plusieurs versions de la directive à utiliser avec plusieurs parties de la navigation.


EDIT 3/18/14: La solution était mal généralisée et s'activerait si vous définissiez un argument pour la valeur de 'activeTab' renvoyant undefined à la fois $location.path() et à l'élément href. Parce que: undefined === undefined. Mis à jour pour corriger cette condition.

En travaillant sur cela, j'ai réalisé qu'il devrait exister 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 à distance au code HTML de style Bootstrap. Mais, il est plus moderne et utilise moins d’éléments, c’est pourquoi je suis partial. Cette version de la directive, ainsi que l'originale, sont maintenant disponible sur Github en tant que module complémentaire que vous pouvez simplement déclarer en tant que dépendance. Je serais heureux de les bower-ize, si quelqu'un les utilise réellement.

Aussi, si vous voulez une version compatible avec le bootstrap qui inclut <li> ', vous pouvez aller avec le module des onglets angular-ui-bootstrap , qui, je pense, est sorti après cet article original, et qui est peut-être encore plus déclaratif que celui-ci. Il est moins concis pour les éléments de base, mais vous offre quelques options supplémentaires, telles que les onglets désactivés et les événements déclaratifs qui se déclenchent lors de l’activation et de la désactivation.

41
XML

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

au lieu de chaque route nécessitant un onglet actif; et ayant besoin de définir l'onglet actif dans chaque contrôleur, je le fais:

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 simplement l'URL qui se rapporte à cette route. Cela supprime simplement la nécessité d'ajouter du code dans chaque contrôleur (glisser dans des dépendances telles que $ 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
Lucas

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

    };

}]);
16
kfis

La solution la plus simple ici:

Comment définir bootstrap _ classe active de la barre de navigation avec Angular JS?

Lequel est:

Utilisez ng-controller pour exécuter un seul contrôleur en dehors de ng-view:

<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 incluez dans controllers.js:

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

Je recommande d'utiliser le module state.ui , qui prend en charge non seulement les vues multiples et imbriquées, mais facilite également ce type de travail (code ci-dessous):

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

Voici une autre version du changement LI de XMLillies w/domi qui utilise une chaîne de recherche au lieu d'un niveau de chemin. Je pense que ce qui se passe pour mon cas d'utilisation est un peu plus évident.

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

J'ai trouvé que la solution de XMLilley était la meilleure, la plus adaptable et la moins intrusive.

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é "if (attrs.href! = Undefined)" parce que cette fonction est appelée deux fois, la seconde fois, une erreur est générée.

Quant au 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
domi

Exemple de bootstrap.

Si vous utilisez le routage intégré Angulars (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 bien que c'était de le faire puisque vous pouvez définir le nom de classe que vous voulez dans votre attribut.

Vous pouvez aussi simplement injecter l'emplacement dans la portée et l'utiliser pour déduire le style de 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
Der Hochstapler

une autre méthode consiste à utiliser i-sref-active

Une directive fonctionnant aux côtés de ui-sref pour ajouter des classes à un élément lorsque l'état de la directive ui-sref associée est active et les supprimer lorsqu'elle est inactive. Le principal cas d'utilisation consiste à simplifier l'apparence spéciale des menus de navigation en utilisant ui-sref, en faisant en sorte que le bouton de menu de l'état "actif" apparaisse différemment, ce qui le distingue des éléments de menu inactifs.

Usage:

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

Exemple:
À partir du 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 de l'application est "app.user" et qu'il contient le paramètre d'état "utilisateur" avec la valeur "bilbobaggins", le code HTML résultant 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 la durée de liaison des directives (toute modification ultérieure de la valeur interpolée est ignorée). Plusieurs classes peuvent être spécifiées dans un format séparé par des espaces.

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

<a ui-sref="home" ui-sref-opts="{reload: true}">Home</a>
2
George Botros

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

échantillon 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 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/

1
jasontwong
'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 utilisez le ci-dessous 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

Je ne me souviens pas où j'ai trouvé cette méthode, mais c'est assez simple et ça 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
cfranklin

J'avais besoin d'une solution qui ne nécessite aucune modification des contrôleurs, car pour certaines pages, nous ne rendons que des modèles et il n'y a aucun contrôleur. Merci aux précédents commentateurs qui ont suggéré d'utiliser $routeChangeSuccess j'ai proposé 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

Cela ne dépend pas des URL et il est donc très facile de le configurer pour n'importe quelle sous-page, etc.

0
szimek

Si vous utilisez ngRoute (pour le routage), votre application aura la configuration ci-dessous,

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, ajoutez juste 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, il ne vous reste plus qu'à ajouter la classe active dans votre balise <li> ou <a>. Comme,

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

Ce qui signifie que chaque fois que l'utilisateur clique sur à propos de la page, cela identifiera automatiquement l'onglet actuel et appliquera la classe css active.

J'espère que ça aide!

0
imbond