Como destacar um item de menu atual?

205

O AngularJS ajuda de alguma forma na definição de uma activeclasse no link da página atual?

Imagino que exista alguma maneira mágica de fazer isso, mas não consigo encontrar.

Meu menu se parece com:

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

e tenho controladores para cada um deles em minhas rotas: TasksControllere ActionsController.

Mas não consigo descobrir uma maneira de vincular a classe "ativa" nos alinks para os controladores.

Alguma dica?

Andriy Drozdyuk
fonte

Respostas:

265

Em Exibiçao

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

no controlador

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

Com isso, o link de tarefas terá a classe ativa em qualquer URL que comece com '/ tasks' (por exemplo, '/ tasks / 1 / reports')

Renan Tomal Fernandes
fonte
4
Isso acabaria correspondendo "/" e "/ qualquer coisa" ou se você tiver vários itens de menu com URLs semelhantes, como "/ test", "/ test / this", "/ test / this / path" se você estiver em / test, destacaria todas essas opções.
Ben Lesh
3
Eu mudei isso para if ($ location.path () == path) e, y path é "/ blah" etc.
Tim
113
Prefiro a notação ngClass="{active: isActive('/tasks')}, onde isActive()retornaria um booleano, pois desacopla o controlador e a marcação / estilo.
Ed Hinchliffe
6
Caso alguém esteja se perguntando sobre o código para não dobrar se o caminho for "/", é isso (desculpe a formatação): $ scope.getClass = function (path) {if ($ location.path (). substr (0, comprimento do caminho) == caminho) {if (caminho == "/" && $ location.path () == "/") {return "active"; } else if (caminho == "/") {return ""; } return "active"} else {return ""}}
1
EdHinchliffe já apontou que isso combina marcação e lógica. Isso também leva à duplicação do caminho e pode ser propenso a copiar e colar erros. Descobri que a abordagem de diretiva do @kfis, embora mais linhas, é mais reutilizável e mantém a marcação mais limpa.
A.Rayay
86

Sugiro usar uma diretiva em um link.

Mas ainda não é perfeito. Cuidado com os hashbangs;)

Aqui está o javascript para diretiva:

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

e aqui está como ele seria usado em 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>

depois estilizando com css:

.active { color: red; }
kfis
fonte
Não sei ao certo o que você quer dizer com "cuidado com os hashbangs". Parece que sempre funcionaria. Você poderia fornecer um contra-exemplo?
Andriy Drozdyuk 19/03/2013
7
Se você estiver tentando usar o Bootstrap e precisar definir com base no hash do href de um a dentro de um li, use var path = $(element).children("a")[0].hash.substring(1);. Isso funcionará para um estilo como<li active-link="active"><a href="#/dashboard">Dashboard</a></li>
Dave
2
Eu mudaria scope.$watch('location.path()', function(newPath) {para scope.$on('$locationChangeStart', function(){.
Sanfilippopablo
2
Se você estiver usando ng-href, basta alterar: var path = attrs.href; para var path = attrs.href||attrs.ngHref;
William Neely
Se você usa o Bootstrap e precisa colocar a classe ativa no <li>, pode mudar element.addClass(clazz);paraelement.parent().addClass(clazz);
JamesRLamar 10/14
47

Aqui está uma abordagem simples que funciona bem com o Angular.

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

Dentro do seu controlador AngularJS:

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

Este tópico possui várias outras respostas semelhantes.

Como definir a classe ativa bootstrap navbar com o Angular JS?

Ender2050
fonte
1
Remova a variável, pois é desnecessária. Basta retornar o resultado da comparação. return viewLocation === $location.path()
afarazit
33

Apenas para adicionar meus dois centavos no debate, criei um módulo angular puro (sem jQuery) e ele também funcionará com URLs de hash contendo dados. (por exemplo #/this/is/path?this=is&some=data)

Você acabou de adicionar o módulo como uma dependência e auto-activea um dos ancestrais do menu. Como isso:

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

E o módulo fica assim:

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

(Você pode, é claro, apenas usar a parte da diretiva)

Também vale a pena notar que isso não funciona para hashes vazios (por exemplo, example.com/#ou apenas example.com), ele precisa ter pelo menos example.com/#/ou apenas example.com#/. Mas isso acontece automaticamente com o ngResource e similares.

E aqui está o violino: http://jsfiddle.net/gy2an/8/

Pylinux
fonte
1
Ótima solução, no entanto, não funcionou no carregamento inicial da página, apenas no localAltere enquanto o aplicativo está ativo. Atualizei seu snippet para lidar com isso.
Jerry
@Jarek: Obrigado! Implementou suas alterações. Eu não tive problemas com isso pessoalmente, mas sua solução parece ser uma boa solução estável para quem deve enfrentar esse problema.
Pylinux 9/04
2
Eu já criou um repo Github para solicitações de pull se mais alguém tem alguma idéia boa: github.com/Karl-Gustav/autoActive
Pylinux
Eu apenas fixo mais alguns erros que ocorreriam se você estivesse usando NG-href .. Isto é encontrado aqui: github.com/Karl-Gustav/autoActive/pull/3
Blake Niemyjski
Seria bom se esse script permitisse especificar um caminho para que você pudesse ativar outros elementos.
Blake Niemyjski
22

No meu caso, resolvi esse problema criando um controlador simples responsável pela navegação

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

E apenas adicionando ng-class ao elemento da seguinte forma:

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

Para usuários do roteador AngularUI :

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

E isso colocará uma activeclasse no objeto selecionado.

frankie4fingers
fonte
2
Esta é uma diretiva de interface do usuário e não funciona se você usar o roteador interno, também conhecido como ngRoute. Dito isto, o roteador da interface do usuário é incrível.
moljac024
Concordo, esqueci de mencionar originalmente que era uma solução apenas para roteador da interface do usuário.
Frankie4fingers
13

Existe uma ng-classdiretiva que liga variável e classe css. Também aceita o objeto (className vs pares de valores booleanos).

Aqui está o exemplo, http://plnkr.co/edit/SWZAqj

Tosh
fonte
Obrigado, mas isso não funcionará com caminhos como:, /test1/blahblahou será?
Andriy Drozdyuk
Então você está dizendo que active: activePath=='/test1'retorna automaticamente um caminho "ativo" que começa com a string especificada? Isso é algum tipo de operador ou regex predefinido?
Andriy Drozdyuk
Desculpe, acho que não entendi sua exigência corretamente. Aqui está meu novo palpite: você deseja destacar o link 'test1' E o link 'test1 / blahblah' destacados quando a rota for 'test1 / blahblah'. "Estou correto? Se esse é o requisito, você está certo que minha solução não é trabalho
26412 Tosh
3
Aqui está o plnkr atualizado: plnkr.co/edit/JI5DtK (que satisfaz os requisitos adivinhados) para mostrar apenas uma solução alternativa.
26412 Tosh
Eu vi o que você fez lá. Mas não sou fã de repetidas ==verificações no html.
Andriy Drozdyuk 19/03/2013
13

A resposta de @ Renan-tomal-fernandes é boa, mas precisava de algumas melhorias para funcionar corretamente. Como sempre, ele sempre detectava o link para a página inicial (/) como acionado, mesmo se você estivesse em outra seção.

Então eu melhorei um pouco, aqui está o código. Eu trabalho com o Bootstrap para que a parte ativa esteja no <li>elemento em vez do <a>.

Controlador

$scope.getClass = function(path) {
    var cur_path = $location.path().substr(0, path.length);
    if (cur_path == path) {
        if($location.path().substr(0).length > 1 && path.length == 1 )
            return "";
        else
            return "active";
    } else {
        return "";
    }
}

Modelo

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

Aqui está a solução que eu encontrei depois de ler algumas das excelentes sugestões acima. Na minha situação específica, eu estava tentando usar o componente de guias do Bootstrap como menu, mas não queria usar a versão da interface do usuário angular porque quero que as guias funcionem como um menu, onde cada guia pode ser marcada como marcador, em vez das guias que atuam como navegação para uma única página. (Consulte http://angular-ui.github.io/bootstrap/#/tabs se você estiver interessado em saber como é a versão da interface do usuário angular das guias de inicialização).

Eu realmente gostei da resposta do kfis sobre a criação de sua própria diretiva para lidar com isso, no entanto, parecia complicado ter uma diretiva que precisava ser colocada em todos os links. Então, eu criei minha própria diretiva Angular, que é colocada uma vez na ul. Apenas no caso de alguém tentar fazer a mesma coisa, pensei em publicá-la aqui, embora, como disse, muitas das soluções acima também funcionem. Essa é uma solução um pouco mais complexa no que diz respeito ao javascript, mas cria um componente reutilizável com marcação mínima.

Aqui está o javascript da diretiva e o provedor de rotas para ng:view:

var app = angular.module('plunker', ['ui.bootstrap']).
  config(['$routeProvider', function($routeProvider) {
    $routeProvider.
        when('/One', {templateUrl: 'one.html'}).
        when('/Two', {templateUrl: 'two.html'}).
        when('/Three', {templateUrl: 'three.html'}).
        otherwise({redirectTo: '/One'});
  }]).
  directive('navTabs', ['$location', function(location) {
    return {
        restrict: 'A',
        link: function(scope, element) {
            var $ul = $(element);
            $ul.addClass("nav nav-tabs");

            var $tabs = $ul.children();
            var tabMap = {};
            $tabs.each(function() {
              var $li = $(this);
              //Substring 1 to remove the # at the beginning (because location.path() below does not return the #)
              tabMap[$li.find('a').attr('href').substring(1)] = $li;
            });

            scope.location = location;
            scope.$watch('location.path()', function(newPath) {
                $tabs.removeClass("active");
                tabMap[newPath].addClass("active");
            });
        }

    };

 }]);

Então, no seu html, você simplesmente:

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

Aqui está o resultado: http://plnkr.co/edit/xwGtGqrT7kWoCKnGDHYN?p=preview .

corinnaerin
fonte
9

Você pode implementar isso de maneira muito simples, aqui está um exemplo:

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

E seu controlador deve ser o seguinte:

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

Eu tive um problema semelhante com o menu localizado fora do escopo do controlador. Não tenho certeza se essa é a melhor solução ou a recomendada, mas é isso que funcionou para mim. Adicionei o seguinte à configuração do meu aplicativo:

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

Então, na visão, tenho:

<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
fonte
Hum ... isso parece mais complicado que a resposta aceita. Você poderia descrever a vantagem disso sobre esse?
Andriy Drozdyuk 19/03/2013
1
Você precisará dele no cenário quando o seu menu estiver fora da ng-view. O controlador de exibição não terá acesso a nada que esteja fora, então usei o $ rootScope para ativar a comunicação. Se o seu menu estiver dentro da visualização ng, não haverá benefícios para você ao usar esta solução.
Março
4

Usando a versão 6 angular com o Bootstrap 4.1

Consegui fazê-lo como mostrado abaixo.

No exemplo abaixo, quando o URL vê '/ contact', o bootstrap ativo é adicionado à tag html. Quando o URL muda, ele é removido.

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

Essa diretiva permite adicionar uma classe CSS a um elemento quando a rota do link se torna ativa.

Leia mais no site da Angular

Jeacovy Gayle
fonte
3

Usando uma diretiva (já que estamos manipulando o DOM aqui), o seguinte é provavelmente o mais próximo de fazer as coisas da "maneira angular":

$scope.timeFilters = [
  {'value':3600,'label':'1 hour'},
  {'value':10800,'label':'3 hours'},
  {'value':21600,'label':'6 hours'},
  {'value':43200,'label':'12 hours'},
  {'value':86400,'label':'24 hours'},
  {'value':604800,'label':'1 week'}
]

angular.module('whatever', []).directive('filter',function(){
return{
    restrict: 'A',
    template: '<li ng-repeat="time in timeFilters" class="filterItem"><a ng-click="changeTimeFilter(time)">{{time.label}}</a></li>',
    link: function linkFn(scope, lElement, attrs){

        var menuContext = attrs.filter;

        scope.changeTimeFilter = function(newTime){
          scope.selectedtimefilter = newTime;

        }

        lElement.bind('click', function(cevent){
            var currentSelection = angular.element(cevent.srcElement).parent();
            var previousSelection = scope[menuContext];

            if(previousSelection !== currentSelection){
                if(previousSelection){
                    angular.element(previousSelection).removeClass('active')
                }
                scope[menuContext] = currentSelection;

                scope.$apply(function(){
                    currentSelection.addClass('active');
                })
            }
        })
    }
}
})

Então o seu HTML ficaria assim:

<ul class="dropdown-menu" filter="times"></ul>
Wesley Hales
fonte
Interessante. Mas menu-itemparece redundante em cada linha. Talvez anexar um atributo ao ulelemento (por exemplo <ul menu>) seria melhor, mas não tenho certeza se isso seria possível.
Andriy Drozdyuk
Acabei de atualizar com uma versão mais recente - em vez de uma lista não ordenada estática, agora estou usando o menu suspenso Boostrap como uma lista de seleção.
Wesley Hales
Isso parece o mais angular idiomático. Parece estar de acordo com os conselhos fornecidos em stackoverflow.com/questions/14994391/… e evita duplicar o caminho na exibição, no href e na classe ng.
fundead
2

Eu fiz assim:

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

Isso permite que você tenha links em uma seção que possui diretiva track-active:

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

Essa abordagem parece muito mais limpa do que outras, para mim.

Além disso, se você estiver usando o jQuery, poderá torná-lo muito mais limpo, porque o jQlite possui apenas suporte básico ao seletor. Uma versão muito mais limpa com o jquery incluído antes da inclusão angular ficaria assim:

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

Aqui está um jsFiddle

konsumer
fonte
2

Minha solução para esse problema, use route.currentno modelo angular.

Como você tem a /tasksrota para destacar em seu menu, você pode adicionar sua própria propriedade menuItemàs rotas declaradas pelo seu módulo:

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

Em seu modelo, tasks.htmlvocê pode usar a seguinte ng-classdiretiva:

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

Na minha opinião, isso é muito mais limpo do que todas as soluções propostas.

François Maturel
fonte
1

Aqui está uma extensão da diretiva kfis que eu fiz para permitir diferentes níveis de correspondência de caminho. Essencialmente, achei a necessidade de corresponder caminhos de URL até uma certa profundidade, pois a correspondência exata não permite o aninhamento e o redirecionamento de estado padrão. Espero que isto ajude.

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

        };
    }]);

E aqui está como eu uso o link

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

Esta diretiva corresponderá ao nível de profundidade especificado no valor do atributo para a diretiva. Apenas significa que pode ser usado em outros lugares muitas vezes.

pkbyron
fonte
1

Aqui está mais uma diretiva para destacar links ativos.

Características principais:

  • Funciona bem com href que contém expressões angulares dinâmicas
  • Compatível com navegação hash-bang
  • Compatível com o Bootstrap, em que a classe ativa deve ser aplicada ao li pai, não ao link em si
  • Permite ativar o link se algum caminho aninhado estiver ativo
  • Permite desativar o link se não estiver ativo

Código:

.directive('activeLink', ['$location', 
function($location) {
    return {
        restrict: 'A',
        link: function(scope, elem, attrs) {
            var path = attrs.activeLink ? 'activeLink' : 'href';
            var target = angular.isDefined(attrs.activeLinkParent) ? elem.parent() : elem;
            var disabled = angular.isDefined(attrs.activeLinkDisabled) ? true : false;
            var nested = angular.isDefined(attrs.activeLinkNested) ? true : false;

            function inPath(needle, haystack) {
                var current = (haystack == needle);
                if (nested) {
                    current |= (haystack.indexOf(needle + '/') == 0);
                }

                return current;
            }

            function toggleClass(linkPath, locationPath) {
                // remove hash prefix and trailing slashes
                linkPath = linkPath ? linkPath.replace(/^#!/, '').replace(/\/+$/, '') : '';
                locationPath = locationPath.replace(/\/+$/, '');

                if (linkPath && inPath(linkPath, locationPath)) {
                    target.addClass('active');
                    if (disabled) {
                        target.removeClass('disabled');
                    }
                } else {
                    target.removeClass('active');
                    if (disabled) {
                        target.addClass('disabled');
                    }
                }
            }

            // watch if attribute value changes / evaluated
            attrs.$observe(path, function(linkPath) {
                toggleClass(linkPath, $location.path());
            });

            // watch if location changes
            scope.$watch(
                function() {
                    return $location.path(); 
                }, 
                function(newPath) {
                    toggleClass(attrs[path], newPath);
                }
            );
        }
    };
}
]);

Uso:

Exemplo simples com expressão angular, digamos $ scope.var = 2 , o link estará ativo se o local for / url / 2 :

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

Exemplo de bootstrap, li pai obterá a classe ativa:

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

Exemplo com URLs aninhados, o link estará ativo se algum URL aninhado estiver ativo (por exemplo, / url / 1 , / url / 2 , url / 1/2 / ... )

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

Exemplo complexo, o link aponta para um URL ( / url1 ), mas estará ativo se outro for selecionado ( / url2 ):

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

Exemplo com link desativado, se não estiver ativo, ele terá a classe 'disabled' :

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

Todos os atributos active-link- * podem ser usados ​​em qualquer combinação, portanto condições muito complexas podem ser implementadas.

Eugene Fidelin
fonte
1

Se você deseja os links para a diretiva em um wrapper em vez de selecionar cada link individual (facilita a visualização do escopo no Batarang), isso também funciona muito bem:

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

A marcação seria apenas:

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

Também devo mencionar que estou usando jQuery 'full-fat' neste exemplo, mas você pode alterar facilmente o que fiz com a filtragem e assim por diante.

marksyzm
fonte
1

Aqui estão meus dois centavos, isso funciona muito bem.

NOTA: Isso não corresponde às páginas filho (que é o que eu precisava).

Visão:

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

Controlador:

// make sure you inject $location as a dependency

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

De acordo com a resposta do @kfis, são comentários e minha recomendação, a diretiva final, conforme abaixo:

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

e aqui está como ele seria usado em 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>

depois estilizando com css:

.active { color: red; }
John_J
fonte
1

Para aqueles que usam ui-router, minha resposta é um pouco semelhante à do Ender2050, mas prefiro fazer isso através do teste de nome de estado:

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

HTML correspondente:

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

Nenhuma das sugestões da diretiva acima foi útil para mim. Se você possui uma barra de navegação de inicialização como esta

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

(que pode ser uma $ yo angularinicialização), você deseja adicionar .activeà lista de classes de elementos pai <li> , não o próprio elemento; ie <li class="active">..</li>. Então eu escrevi isso:

.directive('setParentActive', ['$location', function($location) {
  return {
    restrict: 'A',
    link: function(scope, element, attrs, controller) {
      var classActive = attrs.setParentActive || 'active',
          path = attrs.ngHref.replace('#', '');
      scope.location = $location;
      scope.$watch('location.path()', function(newPath) {
        if (path == newPath) {
          element.parent().addClass(classActive);
        } else {
          element.parent().removeClass(classActive);
        }
      })
    }
  }
}])

uso set-parent-active; .activeé o padrão, portanto, não é necessário definir

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

e o <li>elemento pai será .activequando o link estiver ativo. Para usar uma .activeclasse alternativa como .highlight, simplesmente

<li><a ng-href="#/about" set-parent-active="highlight">About</a></li>
davidkonrad
fonte
Eu tentei o escopo. $ On ("$ routeChangeSuccess", function (evento, atual, anterior) {applyActiveClass ();}); mas só funciona quando o link é clicado e não 'no carregamento da página' (clicando no botão Atualizar). vendo o local funcionou para mim #
585
0

O mais importante para mim foi não alterar o código padrão de auto-inicialização. Aqui é o meu controlador de menu que procura por opções de menu e depois adiciona o comportamento que queremos.

file: header.js
function HeaderCtrl ($scope, $http, $location) {
  $scope.menuLinkList = [];
  defineFunctions($scope);
  addOnClickEventsToMenuOptions($scope, $location);
}

function defineFunctions ($scope) {
  $scope.menuOptionOnClickFunction = function () {
    for ( var index in $scope.menuLinkList) {
      var link = $scope.menuLinkList[index];
      if (this.hash === link.hash) {
        link.parentElement.className = 'active';
      } else {
        link.parentElement.className = '';
      }
    }
  };
}

function addOnClickEventsToMenuOptions ($scope, $location) {
  var liList = angular.element.find('li');
  for ( var index in liList) {
    var liElement = liList[index];
    var link = liElement.firstChild;
    link.onclick = $scope.menuOptionOnClickFunction;
    $scope.menuLinkList.push(link);
    var path = link.hash.replace("#", "");
    if ($location.path() === path) {
      link.parentElement.className = 'active';
    }
  }
}

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

teve o mesmo problema. Aqui está a minha solução :

.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
fonte
0

Acabei de escrever uma diretiva para isso.

Uso:

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

Implementação:

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

Testes:

'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
fonte
0

O percurso:

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

O menu html:

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

O controlador:

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

O problema que encontrei aqui é que o item de menu está ativo apenas quando a página inteira é carregada. Quando a vista parcial é carregada, o menu não muda. Alguém sabe por que isso acontece?

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

Encontrei a solução mais fácil. apenas para comparar indexOf em 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
fonte