Установите активный стиль вкладки с помощью AngularJS



У меня есть маршруты, установленные в AngularJS следующим образом:



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


У меня есть некоторые ссылки на верхней панели в стиле вкладок. Как я могу добавить "активный" класс на вкладку в зависимости от текущего шаблона или url?

428   18  

18 ответов:

способ решить эту проблему без необходимости полагаться на URL-адреса, чтобы добавить пользовательский атрибут для каждого частичного во время $routeProvider конфигурации, вроде этого:

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

разоблачение $route в вашем контроллере:

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

установить active класса на основе текущей активной вкладки:

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

один из способов сделать это было бы с помощью директивы ngClass и $location service. В вашем шаблоне вы можете сделать:

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

здесь isActive будет функция в области, определенной следующим образом:

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

вот полный jsFiddle:http://jsfiddle.net/pkozlowski_opensource/KzAfG/

повторять ng-class="{active:isActive('/dashboard')}" на каждой вкладке навигации может быть утомительно (если у вас много вкладок) так что эта логика может быть кандидатом для очень простой директива.

следуя совету Павла использовать пользовательскую директиву, вот версия, которая не требует добавления полезной нагрузки в routeConfig, является супер декларативной и может быть адаптирована для реагирования на любой уровень пути, просто изменив который slice() на это вы обращаете внимание.

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

мы достигаем наших целей, слушая $routeChangeSuccess событие, а не путем размещения $watch на пути. Я работаю под убеждением, что это означает, что логика должна работать реже, так как я думаю, наблюдает огонь на каждом $digest цикл.

вызовите его, передав свой аргумент уровня пути в объявлении директивы. Это указывает, какой кусок текущего $ location.путь() вы хотите, чтобы соответствовать вашим против.

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

Итак, если ваши вкладки должны реагировать на базовый уровень пути, сделайте аргумент '1'. Таким образом, когда расположение.путь () - это "/ home", он будет соответствовать" #/home " в href. Если у вас есть вкладки, которые должны реагировать на второй уровень, или третий, или 11-й путь, отрегулируйте соответственно. Эта нарезка от 1 или больше обойдет гнусный ' # ' в href, который будет жить в индексе 0.

единственное требование заключается в том, что вы вызываете на <a>, так как элемент предполагает наличие href атрибут, который он будет сравнивать с текущим путем. Однако вы можете довольно легко адаптироваться к чтению / записи родительского или дочернего элемента, если вы предпочитаете вызывать на <li> или что-то. Я копаю это потому, что вы можете повторно использовать его во многих контекстах, просто изменяя аргумент pathLevel. Если глубина для чтения предполагалась в логике, вам потребуется несколько версий директивы для использования с несколькими частями навигации.


EDIT 3/18/14: решение было неадекватно обобщено и активировалось бы, если бы вы определили arg для значения 'activeTab', которое вернуло undefined против $location.path() и элемент href. Потому что: undefined === undefined. Обновлено до исправьте это условие.

работая над этим, я понял, что должна была быть версия, которую вы можете просто объявить на родительском элементе, со структурой шаблона следующим образом:

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

обратите внимание, что эта версия больше не отдаленно напоминает HTML в стиле Bootstrap. Но он более современный и использует меньше элементов, поэтому я неравнодушен к нему. Эта версия директивы, плюс оригинал, теперь доступно на Github как выпадающий модуль вы можете просто объявить как зависимость. Я бы с удовольствием поклонился им, если кто-то действительно использует их.

кроме того, если вы хотите bootstrap-совместимую версию, которая включает в себя <li>'s, Вы можете пойти с угловой-ui-bootstrap Tabs модуль, который, я думаю, вышел после этого оригинального сообщения, и который, возможно, даже более декларативен, чем этот. Он менее лаконичен для основных вещей, но предоставляет вам некоторые дополнительные опции, такие как отключенные вкладки и декларативные события, которые срабатывают при активации и деактивировать.

@rob-juurlink я немного улучшил ваше решение:

вместо каждого маршрута нужна активная вкладка; и нужно установить активную вкладку в каждом контроллере я делаю это:

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

и HTML выглядит так. Activetab - это только url-адрес, который относится к этому маршруту. Это просто устраняет необходимость добавлять код в каждый контроллер (перетаскивание зависимостей, таких как $route и $rootScope, если это единственная причина, по которой они используются)

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

возможно, такая директива может решить вашу проблему: 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);
                }
            });
        }

    };

}]);

самое простое решение здесь:

Как установить bootstrap navbar active class с Angular JS?

что:

используйте ng-контроллер для запуска одного контроллера за пределами 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>

и включить в контроллеры.js:

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

Я рекомендую использовать государство.модуль пользовательского интерфейса которые не только поддерживают несколько и вложенные представления, но и сделать этот вид работы очень легко (код ниже цитируется):

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

стоит почитать.

вот еще одна версия изменения LI XMLillies w / domi, которая использует строку поиска вместо уровня пути. Я думаю, что это немного более очевидно, что происходит для моего случая использования.

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 теперь выглядит так:

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

Я нашел anwser XMLilley самым лучшим и самым адаптируемым и ненавязчивым.

однако у меня был небольшой глюк.

для использования с bootstrap nav, вот как я изменил его:

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

я добавил " if (attrs.хреф!=undefined) " потому что эта функция вызывается дважды, во второй раз создавая ошибку.

Что касается 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>

пример начальной загрузки.

Если вы используете Ангуляры, встроенные в маршрутизацию (ngview), эта директива может быть использована:

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

        }
    };
}]);

предполагая, что ваша разметка выглядит так:

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

мне нравится, что это было делать, так как вы можете установить имя класса, которое вы хотите в свой атрибут.

можно просто введите местоположение в область и использовать это, чтобы вычесть стиль навигация:

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

тогда используйте его в своем ng-class:

<li ng-class="{active: location.path() == '/search'}">
  <a href="/search">Search><a/>
</li>

альтернативный способ-использовать ui-sref-active

директива, работающая вместе с ui-sref для добавления классов к элементу, когда состояние связанной директивы ui-sref активно, и удаления их, когда оно неактивно. Основной вариант использования-упростить специальный внешний вид меню навигации, опираясь на ui-sref, имея кнопку меню "активного" состояния, которая отличается от неактивного меню предметы.

использование:

интерфейс-сref-активный='класс1 класс2 класс_3' - класса "класс1", "класс2", и "класс_3" каждое добавленное к директиве элемента, когда элементы интерфейса, относящиеся к-сref состояние активное, и удаляют, когда он не активен.

пример:
Учитывая следующий шаблон,

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

когда состояние приложения "приложение.user", и содержит параметр состояния "user" со значением "bilbobaggins", в результате чего появится HTML как

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

имя класса интерполируется один раз во время ссылки директив (любые дальнейшие изменения интерполированного значения игнорируются). Несколько классов могут быть указаны через пробел формат.

используйте директиву ui-sref-opts для передачи параметров в состояние$.идти.)( Пример:

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

Я согласен с сообщением Роба о наличии пользовательского атрибута в контроллере. Видимо, у меня недостаточно репутации, чтобы комментировать. Вот jsfiddle, который был запрошен:

пример 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>

примера приложения.js

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

http://jsfiddle.net/HrdR6/

'use strict';

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

и использовать ниже в шаблоне:

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

мне нужно было решение, которое не требует изменений контроллеров, потому что для некоторых страниц мы только визуализируем шаблоны, и нет никакого контроллера вообще. Спасибо предыдущим комментаторам, которые предложили использовать $routeChangeSuccess Я придумал что-то вроде этого:

# 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

это не зависит от URL-адресов, и поэтому его очень легко настроить для любых подстраниц и т. д.

Я не могу вспомнить, где я нашел этот метод, но он довольно прост и хорошо работает.

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

если вы используете ngRoute (для маршрутизации), то ваше приложение будет иметь конфигурацию ниже,

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

теперь, просто добавьте контроллер в этой конфигурации, как показано ниже,

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

как вы упомянули активную вкладку в вашей конфигурации, теперь вам просто нужно добавить активный класс в свой <li> или <a> тег. Мол,

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

что означает, что всякий раз, когда пользователь нажимает на страницу "о программе", это автоматически идентифицирует текущую вкладку и применяет активный css класс.

надеюсь, это поможет!

пришел сюда за решением .. хотя выше решения работают нормально, но нашли их немного сложными ненужными. Для людей, которые все еще ищут простое и аккуратное решение, он отлично справится с задачей.

<section ng-init="tab=1">
                <ul class="nav nav-tabs">
                    <li ng-class="{active: tab == 1}"><a ng-click="tab=1" href="#showitem">View Inventory</a></li>
                    <li ng-class="{active: tab == 2}"><a ng-click="tab=2" href="#additem">Add new item</a></li>
                    <li ng-class="{active: tab == 3}"><a ng-click="tab=3" href="#solditem">Sold item</a></li>
                </ul>
            </section>

Comments

    Ничего не найдено.