Comment mettre en évidence un élément de menu actuel?

205

AngularJS aide-t-il de quelque manière que ce soit à définir une activeclasse sur le lien de la page actuelle?

J'imagine qu'il y a une façon magique de procéder, mais je n'arrive pas à trouver.

Mon menu ressemble à:

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

et j'ai des contrôleurs pour chacun d'eux dans mes itinéraires: TasksControlleret ActionsController.

Mais je ne peux pas trouver un moyen de lier la classe "active" sur les aliens vers les contrôleurs.

Des indices?

Andriy Drozdyuk
la source

Réponses:

265

En vue

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

sur le contrôleur

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

Avec cela, le lien des tâches aura la classe active dans n'importe quelle URL qui commence par «/ tâches» (par exemple «/ tâches / 1 / rapports»)

Renan Tomal Fernandes
la source
4
Cela finirait par correspondre à la fois à "/" et à "/ n'importe quoi" ou si vous avez plusieurs éléments de menu avec des URL similaires, comme "/ test", "/ test / ceci", "/ test / ce / chemin" si vous étiez sur / test, il mettrait en évidence toutes ces options.
Ben Lesh
3
J'ai changé cela en if ($ location.path () == path) et, y path est "/ blah" etc
Tim
113
Je préfère la notation ngClass="{active: isActive('/tasks')}, où isActive()retournerait un booléen car il dissocie le contrôleur et le balisage / style.
Ed Hinchliffe
6
Juste au cas où quelqu'un se demanderait si le code ne double pas si le chemin est "/", c'est le (désolé pour la mise en forme): $ scope.getClass = function (path) {if ($ location.path (). substr (0, path.length) == path) {if (path == "/" && $ location.path () == "/") {return "active"; } else if (path == "/") {return ""; } return "active"} else {return ""}}
1
EdHinchliffe a déjà souligné que cela mélange le balisage et la logique. Cela conduit également à la duplication du chemin et pourrait donc être sujet à des erreurs de copier-coller. J'ai trouvé que l'approche directive de @kfis, bien que plus de lignes, soit plus réutilisable et garde le balisage plus propre.
A. Murray
86

Je suggère d'utiliser une directive sur un lien.

Mais ce n'est pas encore parfait. Attention aux hashbangs;)

Voici le javascript pour la directive:

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

et voici comment il serait utilisé 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>

après styling avec css:

.active { color: red; }
kfis
la source
Je ne sais pas ce que vous entendez par "attention aux hashbangs". Il semble que cela fonctionnerait toujours. Pourriez-vous fournir un contre-exemple?
Andriy Drozdyuk
7
Si vous essayez d'utiliser Bootstrap et que vous devez définir en fonction du hachage du href d'un dans un li, alors utilisez var path = $(element).children("a")[0].hash.substring(1);. Cela fonctionnera pour un style comme<li active-link="active"><a href="#/dashboard">Dashboard</a></li>
Dave
2
Je changerais scope.$watch('location.path()', function(newPath) {pour scope.$on('$locationChangeStart', function(){.
sanfilippopablo
2
Si vous utilisez ng-href, changez simplement: var path = attrs.href; en var path = attrs.href||attrs.ngHref;
William Neely
Si vous utilisez Bootstrap et devez mettre la classe active sur <li>, vous pouvez passer element.addClass(clazz);àelement.parent().addClass(clazz);
JamesRLamar
47

Voici une approche simple qui fonctionne bien avec 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>

Au sein de votre contrôleur AngularJS:

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

Ce fil a un certain nombre d'autres réponses similaires.

Comment définir la classe active bootstrap navbar avec Angular JS?

Ender2050
la source
1
Supprimez la variable car elle n'est pas nécessaire. Renvoyez simplement le résultat de la comparaison. return viewLocation === $location.path()
afarazit
33

Juste pour ajouter mes deux cents dans le débat, j'ai créé un module purement angulaire (pas de jQuery), et il fonctionnera également avec des URL de hachage contenant des données. (par exemple #/this/is/path?this=is&some=data)

Vous venez d'ajouter le module en tant que dépendance et auto-activeà l'un des ancêtres du menu. Comme ça:

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

Et le module ressemble à ceci:

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

(Vous pouvez bien sûr simplement utiliser la partie directive)

Il convient également de noter que cela ne fonctionne pas pour les hachages vides (par exemple example.com/#ou tout simplement example.com) qu'il doit avoir au moins example.com/#/ou juste example.com#/. Mais cela se produit automatiquement avec ngResource et similaires.

Et voici le violon: http://jsfiddle.net/gy2an/8/

Pylinux
la source
1
Excellente solution, mais cela n'a pas fonctionné lors du chargement initial de la page, uniquement sur locationChange pendant que l'application est en ligne. J'ai mis à jour votre extrait pour gérer cela.
Jerry
@Jarek: Merci! Ont mis en œuvre vos modifications. Je n'ai pas eu de problèmes avec cela personnellement, mais votre solution semble être une bonne solution stable pour ceux qui devraient rencontrer ce problème.
Pylinux
2
J'ai maintenant créé un dépôt Github pour les demandes de tirage si quelqu'un d'autre a de bonnes idées: github.com/Karl-Gustav/autoActive
Pylinux
Je viens de corriger quelques bugs supplémentaires qui se produiraient si vous utilisiez ng-href .. Cela se trouve ici: github.com/Karl-Gustav/autoActive/pull/3
Blake Niemyjski
Ce serait bien si ce script vous permettait de spécifier un chemin pour que vous puissiez le faire activer d'autres éléments.
Blake Niemyjski
22

Dans mon cas, j'ai résolu ce problème en créant un simple contrôleur responsable de la navigation

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

Et en ajoutant simplement ng-class à l'élément comme ceci:

<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
la source
14

Pour les utilisateurs du routeur AngularUI :

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

Et cela placera une activeclasse sur l'objet sélectionné.

frankie4fingers
la source
2
Il s'agit d'une directive ui-router et cela ne fonctionne pas si vous utilisez le routeur intégré, alias ngRoute. Cela dit, ui-router est génial.
moljac024
D'accord, j'ai oublié de mentionner à l'origine qu'il s'agissait uniquement d'une solution ui-router.
frankie4fingers
13

Il y a un ng-class directive qui lie la variable et la classe css. Il accepte également l'objet (nom de classe vs paires de valeurs booléennes).

Voici l'exemple, http://plnkr.co/edit/SWZAqj

Tosh
la source
Merci, mais cela ne fonctionnera pas avec des chemins comme:, /test1/blahblahou sera-ce?
Andriy Drozdyuk
Alors, dites-vous que active: activePath=='/test1'renvoie automatiquement un "actif" si le chemin commence par la chaîne donnée? Est-ce une sorte d'opérateur prédéfini ou d'expression régulière?
Andriy Drozdyuk
Désolé, je ne pense pas avoir bien compris votre exigence. Voici ma nouvelle supposition, vous voulez à la fois mettre en évidence le lien 'test1' ET le lien 'test1 / blahblah' mis en surbrillance lorsque la route est 'test1 / blahblah'. "Ai-je raison? Si c'est l'exigence, vous avez raison que ma solution ne travail.
Tosh
3
Voici le plnkr mis à jour: plnkr.co/edit/JI5DtK (qui satisfait l'exigence supposée ) pour simplement montrer une solution alternative.
Tosh
Je vois ce que tu as fait là. Mais je ne suis pas fan des ==contrôles répétés en html.
Andriy Drozdyuk
13

La réponse de @ Renan-tomal-fernandes est bonne, mais avait besoin de quelques améliorations pour fonctionner correctement. En l'état, il détectait toujours le lien vers la page d'accueil (/) comme déclenché, même si vous étiez dans une autre section.

Je l'ai donc un peu amélioré, voici le code. Je travaille avec Bootstrap donc la partie active est dans l' <li>élément au lieu du <a>.

Manette

$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 "";
    }
}

Modèle

<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
la source
10

Voici la solution que j'ai trouvée après avoir lu certaines des excellentes suggestions ci-dessus. Dans ma situation particulière, j'essayais d'utiliser le composant d'onglets Bootstrap comme menu, mais je ne voulais pas utiliser la version Angular-UI de cela parce que je voulais que les onglets agissent comme un menu, où chaque onglet est capable de créer des signets, plutôt que les onglets servant de navigation pour une seule page. (Voir http://angular-ui.github.io/bootstrap/#/tabs si vous êtes intéressé par ce à quoi ressemble la version Angular-UI des onglets de bootstrap).

J'ai vraiment aimé la réponse de kfis à propos de la création de votre propre directive pour gérer cela, mais il semblait difficile d'avoir une directive qui devait être placée sur chaque lien. J'ai donc créé ma propre directive angulaire qui est placée à la place une fois sur leul . Juste au cas où quelqu'un d'autre essaie de faire la même chose, je pensais que je le posterais ici, bien que, comme je l'ai dit, la plupart des solutions ci-dessus fonctionnent également. Il s'agit d'une solution légèrement plus complexe en ce qui concerne le javascript, mais elle crée un composant réutilisable avec un balisage minimal.

Voici le javascript de la directive et le fournisseur d'itinéraire pour 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");
            });
        }

    };

 }]);

Ensuite, dans votre html, vous simplement:

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

Voici le plongeur pour cela: http://plnkr.co/edit/xwGtGqrT7kWoCKnGDHYN?p=preview .

corinnaérine
la source
9

Vous pouvez l'implémenter très simplement, voici un exemple:

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

Et votre contrôleur devrait être le suivant:

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

J'ai eu un problème similaire avec le menu situé en dehors de la portée du contrôleur. Je ne sais pas si c'est la meilleure solution ou une solution recommandée, mais c'est ce qui a fonctionné pour moi. J'ai ajouté ce qui suit à la configuration de mon application:

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

Ensuite, dans la vue, j'ai:

<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
la source
Um ... cela semble plus compliqué que la réponse acceptée. Pourriez-vous décrire l'avantage de ceci sur celui-là?
Andriy Drozdyuk
1
Vous en aurez besoin dans le scénario lorsque votre menu est en dehors de ng-view. Le contrôleur de vue n'aura pas accès à tout ce qui se trouve à l'extérieur, j'ai donc utilisé $ rootScope pour activer la communication. Si votre menu se trouve à l'intérieur de la vue ng, il n'y a aucun avantage à utiliser cette solution.
mrt
4

Utilisation d'Angular Version 6 avec Bootstrap 4.1

J'ai pu le faire comme indiqué ci-dessous.

Dans l'exemple ci-dessous, lorsque l'URL voit «/ contact», le bootstrap actif est alors ajouté à la balise html. Lorsque l'URL change, elle est ensuite supprimée.

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

Cette directive vous permet d'ajouter une classe CSS à un élément lorsque la route du lien devient active.

En savoir plus sur le site angulaire

Jeacovy Gayle
la source
3

En utilisant une directive (puisque nous faisons ici une manipulation DOM), ce qui suit est probablement le plus proche de faire les choses de la "manière angulaire":

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

Votre code HTML ressemblerait alors à:

<ul class="dropdown-menu" filter="times"></ul>
Wesley Hales
la source
Intéressant. Mais menu-itemsemble redondant sur chaque ligne. Il serait peut-être préférable d' attacher un attribut à un ulélément (par exemple <ul menu>), mais je ne suis pas sûr que ce soit possible.
Andriy Drozdyuk
Je viens de mettre à jour avec une version plus récente - au lieu d'une liste statique non ordonnée, j'utilise maintenant le menu déroulant Boostrap comme liste de sélection.
Wesley Hales
Cela ressemble le plus à un angulaire idiomatique. Il semble correspondre aux conseils donnés sur stackoverflow.com/questions/14994391/… , et il évite de dupliquer le chemin dans la vue, dans le href et dans la classe ng.
fundead
2

Je l'ai fait comme ça:

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

Cela vous permet d'avoir des liens dans une section qui a une directive track-active:

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

Cette approche me semble beaucoup plus propre que les autres.

De plus, si vous utilisez jQuery, vous pouvez le rendre beaucoup plus net car jQlite ne prend en charge que le sélecteur de base. Une version beaucoup plus propre avec jquery inclus avant l'inclusion angulaire ressemblerait à ceci:

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

Voici un jsFiddle

consommateur
la source
2

Ma solution à ce problème, utilisez route.currentdans le modèle angulaire.

Comme vous avez l' /tasksitinéraire à mettre en évidence dans votre menu, vous pouvez ajouter votre propre propriété menuItemaux itinéraires déclarés par votre module:

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

Ensuite, dans votre modèle, tasks.htmlvous pouvez utiliser la ng-classdirective suivante :

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

À mon avis, c'est beaucoup plus propre que toutes les solutions proposées.

François Maturel
la source
1

Voici une extension de la directive kfis que j'ai faite pour permettre différents niveaux de correspondance de chemin. Essentiellement, j'ai trouvé la nécessité de faire correspondre les chemins d'URL jusqu'à une certaine profondeur, car la correspondance exacte ne permet pas l'imbrication et les redirections d'état par défaut. J'espère que cela t'aides.

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

        };
    }]);

Et voici comment j'utilise le lien

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

Cette directive correspondra au niveau de profondeur spécifié dans la valeur d'attribut de la directive. Cela signifie simplement qu'il peut être utilisé ailleurs plusieurs fois.

pkbyron
la source
1

Voici encore une autre directive pour mettre en évidence les liens actifs.

Principales caractéristiques:

  • Fonctionne bien avec href qui contient des expressions angulaires dynamiques
  • Compatible avec la navigation hash-bang
  • Compatible avec Bootstrap où la classe active doit être appliquée au parent li et non au lien lui-même
  • Permet de rendre le lien actif si un chemin imbriqué est actif
  • Permet de désactiver le lien s'il n'est pas actif

Code:

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

Usage:

Exemple simple avec une expression angulaire, disons $ scope.var = 2 , alors le lien sera actif si l'emplacement est / url / 2 :

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

Exemple d'amorçage, parent li obtiendra la classe active:

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

Exemple avec des URL imbriquées, le lien sera actif si une URL imbriquée est active (ie / url / 1 , / url / 2 , url / 1/2 / ... )

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

Exemple complexe, le lien pointe vers une URL ( / url1 ) mais sera actif si une autre est sélectionnée ( / url2 ):

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

Exemple avec lien désactivé, s'il n'est pas actif, il aura la classe 'désactivé' :

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

Tous les attributs active-link- * peuvent être utilisés dans n'importe quelle combinaison, de sorte que des conditions très complexes pourraient être implémentées.

Eugene Fidelin
la source
1

Si vous voulez les liens pour la directive dans un wrapper plutôt que de sélectionner chaque lien individuel (facilite la recherche de la portée dans Batarang), cela fonctionne assez bien aussi:

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

Le balisage serait simplement:

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

Je dois également mentionner que j'utilise jQuery «full-fat» dans cet exemple, mais vous pouvez facilement modifier ce que j'ai fait avec le filtrage, etc.

marksyzm
la source
1

Voici mes deux cents, cela fonctionne très bien.

REMARQUE: cela ne correspond pas aux pages enfants (ce dont j'avais besoin).

Vue:

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

Manette:

// make sure you inject $location as a dependency

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

Selon la réponse de @kfis, ce sont des commentaires, et ma recommandation, la directive finale comme ci-dessous:

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

et voici comment il serait utilisé 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>

après styling avec css:

.active { color: red; }
John_J
la source
1

Pour ceux qui utilisent ui-router, ma réponse est quelque peu similaire à celle d'Ender2050, mais je préfère le faire via un test de nom d'état:

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

HTML correspondant:

<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
la source
1

Aucune des suggestions de directive ci-dessus ne m'a été utile. Si vous avez une barre de navigation bootstrap comme celle-ci

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

(qui pourrait être une $ yo angularstartup), vous voulez ajouter .activeà la liste de classes d'élément parent <li> , pas à l'élément lui-même; ie <li class="active">..</li>. J'ai donc écrit ceci:

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

utilisation set-parent-active; .activeest par défaut, il n'est donc pas nécessaire de le définir

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

et l' <li>élément parent sera .activelorsque le lien sera actif. Pour utiliser une .activeclasse alternative comme .highlight, simplement

<li><a ng-href="#/about" set-parent-active="highlight">About</a></li>
davidkonrad
la source
J'avais essayé scope. $ On ("$ routeChangeSuccess", function (event, current, previous) {applyActiveClass ();}); mais cela ne fonctionne que lorsque le lien est cliqué et non pas «au chargement de la page» (en cliquant sur le bouton d'actualisation). regarder l'emplacement a fonctionné pour moi
sawe
0

Le plus important pour moi était de ne pas changer du tout le code par défaut de bootstrap. Ici, c'est mon contrôleur de menu qui recherche les options de menu, puis ajoute le comportement que nous voulons.

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>
user2599258
la source
0

eu le même problème. Voici ma solution :

.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
la source
0

Je viens d'écrire une directive à ce sujet.

Usage:

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

La mise en oeuvre:

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

Tests:

'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
la source
0

La route:

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

Le menu html:

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

Le controlle:

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

Le problème que j'ai trouvé ici est que l'élément de menu n'est actif que lorsque la page entière est chargée. Lorsque la vue partielle est chargée, le menu ne change pas. Quelqu'un sait pourquoi cela se produit?

Mr. D MX
la source
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
la source
0

J'ai trouvé la solution la plus simple. juste pour comparer 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
la source