Установить активный стиль табуляции с помощью AngularJS
У меня есть маршруты, установленные в AngularJS следующим образом:
$routeProvider
.when('/dashboard', {templateUrl:'partials/dashboard', controller:widgetsController})
.when('/lab', {templateUrl:'partials/lab', controller:widgetsController})
У меня есть ссылки на верхнем баре в стиле табуляции. Как я могу добавить "активный" класс на вкладку в зависимости от текущего шаблона или URL?
Ответы
Ответ 1
Способом решения проблемы без необходимости полагаться на 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>
Ответ 2
Один из способов сделать это - использовать директиву ngClass и службу определения местоположения $. В шаблоне вы можете:
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')}"
на каждой вкладке навигации может быть утомительным (если у вас много вкладок), поэтому эта логика может быть кандидатом на очень простую директиву.
Ответ 3
Следуя совету Павла, чтобы использовать пользовательскую директиву, здесь версия, которая требует добавления никакой полезной нагрузки для 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.path() вы хотите сопоставить с вашим атрибутом href
.
<li class="nav_tab"><a href="#/home" detect-active-tab="1">HOME</a></li>
Итак, если ваши вкладки должны реагировать на базовый уровень пути, сделайте аргумент '1'. Таким образом, когда location.path() является "/home", он будет соответствовать "#/home" в href
. Если у вас есть вкладки, которые должны реагировать на второй уровень, или третий или одиннадцатый путь, отрегулируйте соответствующим образом. Эта нарезка от 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 в качестве выпадающего модуля, который вы можете просто объявить как зависимость. Я был бы рад Bower-ize им, если кто-то действительно использует их.
Кроме того, если вы хотите иметь совместимую с bootstrap версию, включающую <li>
, вы можете перейти с angular -ui-bootstrap Tabs module, который, я думаю, вышел после этого оригинального поста и, возможно, даже более декларативный, чем этот. Это менее кратким для базового материала, но предоставляет вам дополнительные опции, такие как заблокированные вкладки и декларативные события, которые запускаются при активации и деактивации.
Ответ 4
@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>
Ответ 5
Возможно, такая директива может решить вашу проблему:
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);
}
});
}
};
}]);
Ответ 6
Самое простое решение здесь:
Как установить загрузочный активный класс navbar с помощью Angular JS?
Что есть:
Используйте ng-controller для запуска одного контроллера вне 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>
и включите в controllers.js:
function HeaderController($scope, $location)
{
$scope.isActive = function (viewLocation) {
return viewLocation === $location.path();
};
}
Ответ 7
Я рекомендую использовать state.ui module, который не только поддерживает множественные и вложенные представления, но и делает эту работу очень простой (код ниже цитируется):
<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>
Стоит прочитать.
Ответ 8
Здесь другая версия изменения XMLillies w/domi LI, которая использует строку поиска вместо уровня пути. Я думаю, что это немного более очевидно, что происходит для моего случая использования.
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>
Ответ 9
Я нашел XMLilley anwser лучшим и наиболее адаптивным и неинтрузивным.
Однако у меня был небольшой сбой.
Для использования с 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.href!= 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>
Ответ 10
Пример начальной загрузки.
Если вы используете Angulars, встроенную в маршрутизацию (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>
Мне это нравится, потому что вы можете указать имя класса в своем атрибуте.
Ответ 11
Вы также можете просто ввести местоположение в область и использовать это, чтобы вычесть стиль для навигации:
function IndexController( $scope, $rootScope, $location ) {
$rootScope.location = $location;
...
}
Затем используйте его в ng-class
:
<li ng-class="{active: location.path() == '/search'}">
<a href="/search">Search><a/>
</li>
Ответ 12
альтернативным способом является использование ui-sref-active
Директива, работающая вместе с ui-sref, добавляет классы к элементу, когда соответствующее директивное состояние ui-sref активно и удаляет их, когда оно неактивно. Основным вариантом использования является упрощение особого вида навигационных меню, основанных на ui-sref, путем нажатия кнопки "активного" состояния, отличающегося от неактивных пунктов меню.
Использование:
ui-sref-active = 'class1 class2 class3' - классы "class1", "class2" и "class3" добавляются к элементу директивы, когда соответствующее состояние ui-sref является активным и удаляется, когда оно неактивным.
Пример:
Учитывая следующий шаблон,
<ul>
<li ui-sref-active="active" class="item">
<a href ui-sref="app.user({user: 'bilbobaggins'})">@bilbobaggins</a>
</li>
<!-- ... -->
</ul>
когда состояние приложения "app.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 для передачи опций в $state.go(). Пример:
<a ui-sref="home" ui-sref-opts="{reload: true}">Home</a>
Ответ 13
Я согласен с сообщением Роба о наличии пользовательского атрибута в контроллере. По-видимому, у меня недостаточно комментариев для комментариев. Здесь запрошен jsfiddle:
sample 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>
sample app.js
angular.module('MyApp', []).config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/a', {
activeNav: 'a'
})
.when('/a/:id', {
activeNav: 'a'
})
.when('/b', {
activeNav: 'b'
})
.when('/c', {
activeNav: 'c'
});
}])
.controller('MyCtrl', function ($scope, $route) {
$scope.$route = $route;
$scope.links = [{
uri: '#/a',
name: 'A',
type: 'a'
}, {
uri: '#/b',
name: 'B',
type: 'b'
}, {
uri: '#/c',
name: 'C',
type: 'c'
}, {
uri: '#/a/detail',
name: 'A Detail',
type: 'a'
}];
});
http://jsfiddle.net/HrdR6/
Ответ 14
'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> {{menuItem.title}}</a></li>
Ответ 15
Мне понадобилось решение, которое не требует изменений в контроллерах, потому что для некоторых страниц мы обрабатываем только шаблоны и нет контроллера вообще. Благодаря предыдущим комментаторам, которые предложили использовать $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-адресов, и поэтому очень легко настроить его для любых дополнительных страниц и т.д.
Ответ 16
Я не могу вспомнить, где я нашел этот метод, но он довольно прост и работает хорошо.
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;
}
Ответ 17
Пришел сюда для решения.. хотя выше решения работают нормально, но нашли их немного сложными ненужными. Для людей, которые все еще ищут легкое и аккуратное решение, он отлично справится с задачей.
<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>