使用 AngularJS 设置活动标签样式
Posted
技术标签:
【中文标题】使用 AngularJS 设置活动标签样式【英文标题】:Set active tab style with AngularJS 【发布时间】:2012-08-31 00:45:21 【问题描述】:我在 AngularJS 中设置了这样的路由:
$routeProvider
.when('/dashboard', templateUrl:'partials/dashboard', controller:widgetsController)
.when('/lab', templateUrl:'partials/lab', controller:widgetsController)
我在顶部栏有一些链接样式为选项卡。如何根据当前模板或 url 将“活动”类添加到选项卡?
【问题讨论】:
How do I implement the bootstrap navbar active class with Angular JS的可能重复 @AminMeyghani 这个问题怎么可能与 之后 差不多一年后提出的问题重复? 【参考方案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>
【讨论】:
这是迄今为止我见过的最好的解决方案,因为它支持像 /foo/:bar 这样的动态 url。 我实际上无法让它工作。你能提供一个plnkr吗? 只有一件事:最好设置$scope.activeTab = $route.current.activetab
,这样你就可以让html更干净一点。
这在 AngularJS 1.0.8 中不起作用。 $route.current 未定义。
将此与@Lucas 的$rootScope
下面的技巧结合起来,使其在所有范围内都可用。【参考方案2】:
一种方法是使用 ngClass 指令和 $location 服务。在您的模板中,您可以这样做:
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')"
可能会很乏味(如果您有很多选项卡),因此此逻辑可能适合非常简单的指令。
【讨论】:
我花了很长时间才发现“非常简单的指令”实际上写起来非常简单,所以我在下面提供了一个。 :-) 它应该可以在各种上下文中重用,没有非声明性配置。 查看 jsFiddle,如何设置当前页面在页面加载时处于活动状态?该示例仅在用户单击选项时有效。例如,您可能希望在从外部链接登陆主页时突出显示“主页”导航。 啊,这让我有点摸不着头脑。谢谢!【参考方案3】:按照 Pavel 的建议使用自定义指令,这里的版本不需要向 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”时,它将匹配href
中的“#/home”。如果您有应该对路径的第二级、第三级或第 11 级作出反应的选项卡,请相应地进行调整。这种从 1 或更大的切片将绕过 href 中邪恶的“#”,它将位于索引 0 处。
唯一的要求是您在<a>
上调用,因为该元素假定存在href
属性,它将与当前路径进行比较。但是,如果您喜欢在 <li>
或其他东西上调用,您可以相当容易地适应读/写父元素或子元素。我挖掘这一点是因为您可以通过简单地改变 pathLevel 参数在许多情况下重复使用它。如果在逻辑中假设要读取的深度,则需要多个版本的指令才能与导航的多个部分一起使用。
编辑 3/18/14:解决方案没有得到充分概括,如果您为返回 undefined
的 'activeTab' 值定义了一个 arg,该值针对 $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>
请注意,此版本不再类似于 Bootstrap 样式的 HTML。但是,它更现代,使用的元素更少,所以我偏爱它。这个版本的指令,加上原来的,现在是available on Github 作为一个插入模块,你可以声明为一个依赖项。如果有人真正使用它们,我很乐意对它们进行 Bower-ize。
另外,如果你想要一个包含 <li>
的引导兼容版本,你可以使用 angular-ui-bootstrap Tabs module,我认为它是在这篇原始帖子之后出现的,它可能比这篇文章更具声明性.它对于基本的东西不太简洁,但为您提供了一些额外的选项,例如禁用的选项卡和在激活和停用时触发的声明性事件。
【讨论】:
我不敢相信没有人真的给这个投票!这是我的 2 美分。虽然代码中有一个小错误,但我认为“tabLevel”应该是“activeTab”。对于 Bootstrap 样式,您可能希望将“活动”类添加到 LI 元素而不是 A 元素。但这只需要稍作改动。 您对 activeTab 的看法是绝对正确的,@DavidLin。已编辑。但是,我不喜欢 Bootstrap 的结构,因此存在故意的差异。事实上,我开始认为导航抽象可能根本不属于ul
,也许应该只是锚的集合,由nav
或其他分组元素包裹。处理li
的中间层会增加复杂性而没有任何回报,特别是现在我们可以使用nav
元素来明确发生了什么。
这很简单,也很棒。我很惊讶 Angular 中还没有这样的东西来检查你所在的路线。
要使其与 bootstrap3 一起工作,您只需将 `element.addClass("active");` 更改为 ` element.parent('li').addClass("active") ;` 我认为它可以更好地命名,例如 is-active-tab insead of active-tab 似乎声明该选项卡处于活动状态。否则,这是一个非常好的指令。请参阅@domi 的答案中的这种变化
此页面上的最佳解决方案,不敢相信它的支持率如此之低。【参考方案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>
【讨论】:
非常感谢您的修改。很不错。您对在页面首次加载时设置活动标签有什么建议吗? 取决于你想要什么。通常你会有'/' url 作为你的主控制器。这样,当用户访问您的网址时,它将加载该控制器并将该选项卡设置为活动状态。在上面的示例中,我没有'/' url,所以如果这是你的情况,只需添加一个 .otherwise() $routeProvider。 when('/dashboard', templateUrl: 'partials/dashboard.html', 控制器: Ctrl1 ). when('/lab', templateUrl: 'partials/lab.html', 控制器: Ctrl2 ).otherwise( redirectTo: '/dashboard' );祝你好运! 非常感谢@Lucas。这有帮助。出于某种原因,我必须将 # 符号添加到我的主路由中 - when('#/', controller: FormsController, templateUrl: 'partials/dashboard.html' )。 我更喜欢这种方式。拥有一个 rootScope 并在任何地方做任何事情【参考方案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);
);
;
]);
【讨论】:
请注意,如果 href 包含表达式:docs.angularjs.org/guide/directive#Attributes,则必须使用 $observe。查看更新的小提琴:jsfiddle.net/p3ZMR/10【参考方案6】:这里最简单的解决方案:
How to set bootstrap navbar active class with 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,它使用搜索字符串而不是路径级别进行了 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 是最好的、适应性最强且非侵入性的。
但是我有一个小故障。
为了与引导导航一起使用,我修改它的方法如下:
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>
【讨论】:
nvm,这是我的错,这被调用了两次。我猜“if(attrs.href!=undefined)”是不需要的。【参考方案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】:您也可以简单地inject the location into the scope 并使用它来扣除导航的样式:
function IndexController( $scope, $rootScope, $location )
$rootScope.location = $location;
...
然后在你的ng-class
中使用它:
<li ng-class="active: location.path() == '/search'">
<a href="/search">Search><a/>
</li>
【讨论】:
标记中不应该是$root.location.path()吗? @Irshu:这可能更干净,但上述方法也适用于我。【参考方案12】:另一种方法是使用ui-sref-active
与 ui-sref 一起工作的指令,用于在相关 ui-sref 指令的状态为活动时向元素添加类,并在相关的 ui-sref 指令状态为非活动时删除它们。主要用例是简化依赖 ui-sref 的导航菜单的特殊外观,通过使“活动”状态的菜单按钮看起来不同,将其与非活动菜单项区分开来。
用法:
ui-sref-active='class1 class2 class3' - 当相关的 ui-sref 的状态为活动时,类 "class1"、"class2" 和 "class3" 分别添加到指令元素中,并在相关时移除不活动。
示例: 给定以下模板,
<ul>
<li ui-sref-active="active" class="item">
<a href ui-sref="app.user(user: 'bilbobaggins')">@bilbobaggins</a>
</li>
<!-- ... -->
</ul>
当应用状态为“app.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 指令将选项传递给 $state.go()。示例:
<a ui-sref="home" ui-sref-opts="reload: true">Home</a>
【讨论】:
谢谢。在 ionic 框架中工作时非常有用!【参考方案13】:我同意 Rob 关于在控制器中有自定义属性的帖子。显然我没有足够的代表发表评论。这是请求的 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>
示例 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】:如果您使用 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 类。
我希望这会有所帮助!
【讨论】:
【参考方案18】:来到这里寻求解决方案.. 虽然上述解决方案运行良好,但发现它们有点复杂,没有必要。对于仍在寻找简单而简洁的解决方案的人来说,它可以完美地完成任务。
<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>
【讨论】:
以上是关于使用 AngularJS 设置活动标签样式的主要内容,如果未能解决你的问题,请参考以下文章
AngularJs中,如何在render完成之后,执行Js脚本