How to highlight a current menu item? - angularjs

Does AngularJS help in any way with setting an active class on the link for the current page?
I imagine there is some magical way this is done, but I can't seem to find.
My menu looks like:
<ul>
<li><a class="active" href="/tasks">Tasks</a>
<li>Tasks
</ul>
and I have controllers for each of them in my routes: TasksController and ActionsController.
But I can't figure out a way to bind the "active" class on the a links to the controllers.
Any hints?

on view
<a ng-class="getClass('/tasks')" href="/tasks">Tasks</a>
on controller
$scope.getClass = function (path) {
return ($location.path().substr(0, path.length) === path) ? 'active' : '';
}
With this the tasks link will have the active class in any url that starts with '/tasks'(e.g. '/tasks/1/reports')

I suggest using a directive on a link.
But its not perfect yet. Watch out for the hashbangs ;)
Here is the javascript for directive:
angular.module('link', []).
directive('activeLink', ['$location', function (location) {
return {
restrict: 'A',
link: function(scope, element, attrs, controller) {
var clazz = attrs.activeLink;
var path = attrs.href;
path = path.substring(1); //hack because path does not return including hashbang
scope.location = location;
scope.$watch('location.path()', function (newPath) {
if (path === newPath) {
element.addClass(clazz);
} else {
element.removeClass(clazz);
}
});
}
};
}]);
and here is how it would be used in html:
<div ng-app="link">
One
One
home
</div>
afterwards styling with css:
.active { color: red; }

Here's a simple approach that works well with Angular.
<ul>
<li ng-class="{ active: isActive('/View1') }">View 1</li>
<li ng-class="{ active: isActive('/View2') }">View 2</li>
<li ng-class="{ active: isActive('/View3') }">View 3</li>
</ul>
Within your AngularJS controller:
$scope.isActive = function (viewLocation) {
var active = (viewLocation === $location.path());
return active;
};
This thread has a number of other similar answers.
How to set bootstrap navbar active class with Angular JS?

Just to add my two cents in the debate I have made a pure angular module (no jQuery), and it will also work with hash urls containing data. (e.g. #/this/is/path?this=is&some=data)
You just add the module as a dependency and auto-active to one of the ancestors of the menu. Like this:
<ul auto-active>
<li>main</li>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
And the module look like this:
(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);
}
}
}]);
}());
(You can of course just use the directive part)
It's also worth noticing that this doesn't work for empty hashes (e.g. example.com/# or just example.com) it needs to have at least example.com/#/ or just example.com#/. But this happens automatically with ngResource and the like.
And here is the fiddle: http://jsfiddle.net/gy2an/8/

In my case I resolved this problem by creating a simple controller responsible for the navigation
angular.module('DemoApp')
.controller('NavigationCtrl', ['$scope', '$location', function ($scope, $location) {
$scope.isCurrentPath = function (path) {
return $location.path() == path;
};
}]);
And by just adding ng-class to the element like so:
<ul class="nav" ng-controller="NavigationCtrl">
<li ng-class="{ active: isCurrentPath('/') }">Home</li>
<li ng-class="{ active: isCurrentPath('/about') }">About</li>
<li ng-class="{ active: isCurrentPath('/contact') }">Contact</li>
</ul>

For AngularUI Router users:
<a ui-sref-active="active" ui-sref="app">
And that will place an active class on the object that is selected.

There is a ng-class directive, which binds variable and css class.
It also accepts the object (className vs bool value pairs).
Here is the example, http://plnkr.co/edit/SWZAqj

The answer from #Renan-tomal-fernandes is good, but needed a couple of improvements to work correctly.
As it was, it'd always detect the link to the home page ( / ) as triggered, even if you were in another section.
So I improved it a little bit, here's the code.
I work with Bootstrap so the active part is in the <li> element instead of the the <a>.
Controller
$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 "";
}
}
Template
<div class="nav-collapse collapse">
<ul class="nav">
<li ng-class="getClass('/')">Home</li>
<li ng-class="getClass('/contents/')">Contents</li>
<li ng-class="getClass('/data/')">Your data</li>
</ul>
</div>

Here is the solution that I came up with after reading some of the excellent suggestions above. In my particular situation, I was trying to use Bootstrap tabs component as my menu, but didn't want to use the Angular-UI version of this because I want the tabs to act as a menu, where each tab is bookmark-able, rather than the tabs acting as navigation for a single page. (See http://angular-ui.github.io/bootstrap/#/tabs if you're interested in what the Angular-UI version of bootstrap tabs looks like).
I really liked kfis's answer about creating your own directive to handle this, however it seemed cumbersome to have a directive that needed to be placed on every single link. So I've created my own Angular directive which is placed instead once on the ul. Just in case any one else is trying to do the same thing, I thought I'd post it here, though as I said, many of the above solutions work as well. This is a slightly more complex solution as far as the javascript goes, but it creates a reusable component with minimal markup.
Here is the javascript for the directive and the route provider for 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");
});
}
};
}]);
Then in your html you simply:
<ul nav-tabs>
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul>
<ng:view><!-- Content will appear here --></ng:view>
Here's the plunker for it: http://plnkr.co/edit/xwGtGqrT7kWoCKnGDHYN?p=preview.

You can implement this very simply, here is an example:
<div ng-controller="MenuCtrl">
<ul class="menu">
<li ng-class="menuClass('home')">Page1</li>
<li ng-class="menuClass('about')">Page2</li>
</ul>
</div>
And your Controller should be this:
app.controller("MenuCtrl", function($scope, $location) {
$scope.menuClass = function(page) {
var current = $location.path().substring(1);
return page === current ? "active" : "";
};
});

use angular-ui-router's ui-sref-active directive
https://github.com/angular-ui/ui-router/wiki/Quick-Reference#statename
<ul>
<li ui-sref-active="active" class="item">
<a href ui-sref="app.user({user: 'bilbobaggins'})">#bilbobaggins</a>
</li>
<!-- ... -->
</ul>

Using Angular Version 6 with Bootstrap 4.1
I was able to get it done like as seen below.
In the example below, when the URL sees '/contact', the bootstrap active is then added to the html tag. When the URL changes it is then removed.
<ul>
<li class="nav-item" routerLink="/contact" routerLinkActive="active">
<a class="nav-link" href="/contact">Contact</a>
</li>
</ul>
This directive lets you add a CSS class to an element when the link's
route becomes active.
Read more on Angular website

I had similar problem with menu located outside the controller scope. Not sure if this is best solution or a recommended one but this is what worked for me. I've added the following to my app configuration:
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;
}
}
});
Then in the view I have:
<li>Home</li>
<li><a href="/register" ng-class="{true: 'active'}[menuActive('/register')]">
<li>...</li>

Using a directive (since we are doing DOM manipulation here) the following is probably the closest to doing things the "angular way":
$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');
})
}
})
}
}
})
Then your HTML would look like:
<ul class="dropdown-menu" filter="times"></ul>

I did it like this:
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};
});
This enables you to have links in a section that has track-active directive:
<nav track-active>
Page 1
Page 2
Page 3
</nav>
This approach seems much cleaner than others, to me.
Also, if you are using jQuery, you can make it a lot neater because jQlite only has basic selector support. A much cleaner version with jquery included before angular include would look like this:
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};
});
Here is a jsFiddle

My solution to this problem, use route.current in the angular template.
As you have the /tasks route to highlight in your menu, you can add your own property menuItem to the routes declared by your module:
$routeProvider.
when('/tasks', {
menuItem: 'TASKS',
templateUrl: 'my-templates/tasks.html',
controller: 'TasksController'
);
Then in your template tasks.htmlyou can use following ng-class directive:
<a href="app.html#/tasks"
ng-class="{active : route.current.menuItem === 'TASKS'}">Tasks</a>
In my opinion, this is much cleaner than all proposed solutions.

Here is an extension on kfis directive that I did to allow for different levels of path matching. Essentially I found the need for matching URL paths upto a certain depth, as exact matching doesn't allow for nesting and default state redirections. Hope this helps.
.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");
}
});
}
};
}]);
And here is how I use the link
<nav>
Project
Company
Person
</nav>
This directive will match the depth level specified in the attribute value for the directive. Just means it can be used elsewhere many times over.

Here is yet another directive to highlight active links.
Key features:
Works fine with href that contains dynamic angular expressions
Compatible with hash-bang navigation
Compatible with Bootstrap where active class should be applied to parent li not the link itself
Allows make link active if any nested path is active
Allows make link disabled if it is not active
Code:
.directive('activeLink', ['$location',
function($location) {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
var path = attrs.activeLink ? 'activeLink' : 'href';
var target = angular.isDefined(attrs.activeLinkParent) ? elem.parent() : elem;
var disabled = angular.isDefined(attrs.activeLinkDisabled) ? true : false;
var nested = angular.isDefined(attrs.activeLinkNested) ? true : false;
function inPath(needle, haystack) {
var current = (haystack == needle);
if (nested) {
current |= (haystack.indexOf(needle + '/') == 0);
}
return current;
}
function toggleClass(linkPath, locationPath) {
// remove hash prefix and trailing slashes
linkPath = linkPath ? linkPath.replace(/^#!/, '').replace(/\/+$/, '') : '';
locationPath = locationPath.replace(/\/+$/, '');
if (linkPath && inPath(linkPath, locationPath)) {
target.addClass('active');
if (disabled) {
target.removeClass('disabled');
}
} else {
target.removeClass('active');
if (disabled) {
target.addClass('disabled');
}
}
}
// watch if attribute value changes / evaluated
attrs.$observe(path, function(linkPath) {
toggleClass(linkPath, $location.path());
});
// watch if location changes
scope.$watch(
function() {
return $location.path();
},
function(newPath) {
toggleClass(attrs[path], newPath);
}
);
}
};
}
]);
Usage:
Simple example with angular expression, lets say $scope.var = 2, then link will be active if location is /url/2 :
<a href="#!/url/{{var}}" active-link>
Bootstrap example, parent li will get active class:
<li>
<a href="#!/url" active-link active-link-parent>
</li>
Example with nested urls, link will be active if any nested url is active (i.e. /url/1, /url/2, url/1/2/...)
<a href="#!/url" active-link active-link-nested>
Complex example, link points to one url (/url1) but will be active if another is selected (/url2):
<a href="#!/url1" active-link="#!/url2" active-link-nested>
Example with disabled link, if it is not active it will have 'disabled' class:
<a href="#!/url" active-link active-link-disabled>
All active-link-* attributes can be used in any combination, so very complex conditions could be implemented.

If you want the links for the directive in a wrapper rather than selecting each individual link (makes it easier to look at the scope in Batarang), this works pretty well too:
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);
});
}
};
}
]);
Markup would just be:
<nav role="navigation" data-navigation>
<ul>
<li>Messages</li>
<li>Help</li>
<li>Details</li>
</ul>
</nav>
I should also mention that I am using 'full-fat' jQuery in this example, but you can easily alter what I have done with the filtering and so on.

Here's my two cents, this works just fine.
NOTE: This does not match childpages (which is what I needed).
View:
<a ng-class="{active: isCurrentLocation('/my-path')}" href="/my-path" >
Some link
</a>
Controller:
// make sure you inject $location as a dependency
$scope.isCurrentLocation = function(path){
return path === $location.path()
}

According to #kfis 's answer, it's comments, and my recommend, the final directive as below:
.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);
}
});
}
};
}]);
and here is how it would be used in html:
<div ng-app="link">
One
One
home
</div>
afterwards styling with css:
.active { color: red; }

For those using ui-router, my answer is somewhat similar to Ender2050's, but I prefer doing this via state name testing:
$scope.isActive = function (stateName) {
var active = (stateName === $state.current.name);
return active;
};
corresponding HTML:
<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>

None of the above directive suggestions were useful to me. If you have a bootstrap navbar like this
<ul class="nav navbar-nav">
<li><a ng-href="#/">Home</a></li>
<li><a ng-href="#/about">About</a></li>
...
</ul>
(that could be a $ yo angular startup) then you want to add .active to the parent <li> element class list, not the element itself; i.e <li class="active">..</li>. So I wrote this :
.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);
}
})
}
}
}])
usage set-parent-active; .active is default so not needed to be set
<li><a ng-href="#/about" set-parent-active>About</a></li>
and the parent <li> element will be .active when the link is active. To use an alternative .active class like .highlight, simply
<li><a ng-href="#/about" set-parent-active="highlight">About</a></li>

Most important for me was not to change at all the bootstrap default code.
Here it is my menu controller that search for menu options and then add the behavior we want.
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>PLATFORMS</li>
<li>FUNCTIONAL TESTS</li>
</ul>
</div>
</div>
</div>
</div>

had same problem. Here is my solution:
.directive('whenActive',
[
'$location',
($location)->
scope: true,
link: (scope, element, attr)->
scope.$on '$routeChangeSuccess',
() ->
loc = "#"+$location.path()
href = element.attr('href')
state = href.indexOf(loc)
substate = -1
if href.length > 3
substate = loc.indexOf(href)
if loc.length is 2
state = -1
#console.log "Is Loc: "+loc+" in Href: "+href+" = "+state+" and Substate = "+substate
if state isnt -1 or substate isnt -1
element.addClass 'selected'
element.parent().addClass 'current-menu-item'
else if href is '#' and loc is '#/'
element.addClass 'selected'
element.parent().addClass 'current-menu-item'
else
element.removeClass 'selected'
element.parent().removeClass 'current-menu-item'
])

I just wrote a directive for this.
Usage:
<ul class="nav navbar-nav">
<li active>Link 1</li>
<li active>Link 2</li>
</ul>
Implementation:
angular.module('appName')
.directive('active', function ($location, $timeout) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
// Whenever the user navigates to a different page...
scope.$on('$routeChangeSuccess', function () {
// Defer for other directives to load first; this is important
// so that in case other directives are used that this directive
// depends on, such as ng-href, the href is evaluated before
// it's checked here.
$timeout(function () {
// Find link inside li element
var $link = element.children('a').first();
// Get current location
var currentPath = $location.path();
// Get location the link is pointing to
var linkPath = $link.attr('href').split('#').pop();
// If they are the same, it means the user is currently
// on the same page the link would point to, so it should
// be marked as such
if (currentPath === linkPath) {
$(element).addClass('active');
} else {
// If they're not the same, a li element that is currently
// marked as active needs to be "un-marked"
element.removeClass('active');
}
});
});
}
};
});
Tests:
'use strict';
describe('Directive: active', function () {
// load the directive's module
beforeEach(module('appName'));
var element,
scope,
location,
compile,
rootScope,
timeout;
beforeEach(inject(function ($rootScope, $location, $compile, $timeout) {
scope = $rootScope.$new();
location = $location;
compile = $compile;
rootScope = $rootScope;
timeout = $timeout;
}));
describe('with an active link', function () {
beforeEach(function () {
// Trigger location change
location.path('/foo');
});
describe('href', function () {
beforeEach(function () {
// Create and compile element with directive; note that the link
// is the same as the current location after the location change.
element = angular.element('<li active>Foo</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>Foo</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>Foo</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();
});
});
});

The route:
$routeProvider.when('/Account/', { templateUrl: '/Home/Account', controller: 'HomeController' });
The menu html:
<li id="liInicio" ng-class="{'active':url=='account'}">
The controller:
angular.module('Home').controller('HomeController', function ($scope, $http, $location) {
$scope.url = $location.url().replace(/\//g, "").toLowerCase();
...
The problem I found here is that the menu item is active only when the full page is loaded. When the partial view is loaded the menu doesn't change. Somebody knows why it happens?

$scope.getClass = function (path) {
return String(($location.absUrl().split('?')[0]).indexOf(path)) > -1 ? 'active' : ''
}
<li class="listing-head" ng-class="getClass('/v/bookings')">MY BOOKING</li>
<li class="listing-head" ng-class="getClass('/v/fleets')">MY FLEET</li>
<li class="listing-head" ng-class="getClass('/v/adddriver')">ADD DRIVER</li>
<li class="listing-head" ng-class="getClass('/v/bookings')">INVOICE</li>
<li class="listing-head" ng-class="getClass('/v/profile')">MY PROFILE</li>
<li class="listing-head">LOG OUT</li>

I found the easiest solution. just to compare indexOf in 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>

Related

Disable ui-sref based on a condition

Can you tell me a way to disable the submit button, which changes to a new state by:
<a ui-sref="state">Submit</a>
The button should be enabled only when the form is valid.
ng-disabled with ui-sref does not work:
<form name="tickets">
<button ng-disabled="!canSave()"><a ui-sref="view">Submit</a></button>
</form>
canSave function inside app.js being:
$scope.canSave = function(){
return $scope.tickets.$dirty && $scope.tickets.$valid;
};
You could simply pair it with ng-click so that ng-disabled will work.
.controller('myCtrl', function($scope, $state) {
// so that you can call `$state.go()` from your ng-click
$scope.go = $state.go.bind($state);
})
<!-- call `go()` and pass the state you want to go to -->
<button ng-disabled="!canSave()" ng-click="go('view')>Submit</button>
Here's a more fancy way using a custom directive:
angular.module('myApp', ['ui.router'])
.config(function($stateProvider) {
$stateProvider.state('home', {
url: '/'
});
})
.controller('myCtrl', function() {
})
.directive('uiSrefIf', function($compile) {
return {
scope: {
val: '#uiSrefVal',
if: '=uiSrefIf'
},
link: function($scope, $element, $attrs) {
$element.removeAttr('ui-sref-if');
$compile($element)($scope);
$scope.$watch('if', function(bool) {
if (bool) {
$element.attr('ui-sref', $scope.val);
} else {
$element.removeAttr('ui-sref');
$element.removeAttr('href');
}
$compile($element)($scope);
});
}
};
})
;
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.2/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.13/angular-ui-router.min.js"></script>
<div ng-app="myApp" ng-controller="myCtrl">
<form name="form">
<input ng-model="foo" ng-required="true">
<button ng-disabled="form.$invalid">
<a ui-sref-if="!form.$invalid" ui-sref-val="home">test</a>
</button>
</form>
</div>
There is a pure CSS solution for the problem.
Just set the pointer-events on your a tag to none:
button[disabled] > a {
pointer-events: none;
}
Of course you should set a more accurate CSS selector to target just the buttons you want.
My take on the directive provided by #m59 (without introducing an isolated scope):
.directive('uiSrefIf', function($compile) {
return {
link: function($scope, $element, $attrs) {
var uiSrefVal = $attrs.uiSrefVal,
uiSrefIf = $attrs.uiSrefIf;
$element.removeAttr('ui-sref-if');
$element.removeAttr('ui-sref-val');
$scope.$watch(
function(){
return $scope.$eval(uiSrefIf);
},
function(bool) {
if (bool) {
$element.attr('ui-sref', uiSrefVal);
} else {
$element.removeAttr('ui-sref');
$element.removeAttr('href');
}
$compile($element)($scope);
}
);
}
};
})
The easiest way that I found to do it conditionally in the template
When your condition is met, i point it to the current state which doesn't activate the transition else I transition to the state needed.
<a ui-sref="{{ 1==1 ? '.': 'state({param:here})'}}">Disabled Link</a>
I had problems with the isolate scope and using other directives inside anchors, so here's my take, replacing the iscolate scope with regular attributes.
angular.module('app').directive('uiSrefIf', ['$compile', function($compile) {
var directive = {
restrict: 'A',
link: linker
};
return directive;
function linker(scope, elem, attrs) {
elem.removeAttr('ui-sref-if');
$compile(elem)(scope);
scope.$watch(attrs.condition, function(bool) {
if (bool) {
elem.attr('ui-sref', attrs.value);
} else {
elem.removeAttr('ui-sref');
elem.removeAttr('href');
}
$compile(elem)(scope);
});
}
}]);
Html Looks like this.
<a ui-sref-if condition="enableLink()" value="init.main">
<cover class="card-image" card="card"></cover>
</a>
ui-router v1.0.18 introduced support for ng-disabled on anchor tags.
in ui-sref you can try and hide url based on codition for example i have this code and it works in urls
<a ui-sref="category-edit({pk:category.id})" ng-show="canEdit(category.owner)" class="btn btn-primary"><i class="glyphicon glyphicon-pencil"></i></a>
and in my controller
$scope.canEdit = function(category){
return category == AuthUser.username ;
}
Authuser is a factory in my main page
<script>
// Configure the current user
var app = angular.module('myApp'); // Not including a list of dependent modules (2nd parameter to `module`) "re-opens" the module for
app.factory('AuthUser', function() {
return {
username: "{{ user.username|default:''|escapejs }}"
}
});
</script>

Dynamically set the value of ui-sref Angularjs

I have searched for a similar question but the ones that came up seem slightly different.
I am trying to change the ui-sref='' of a link dynamically (this link points to the next section of a wizard form and the next section depends on the selection made on the dropdown list). I am simply trying to set the ui-sref attribute depending on some selection in a select box. I am able to change the ui-sref by binding to a scope attribute which is set when a selection is made. however the link does not work, is this possible at all? thanks
<a ui-sref="form.{{url}}" >Next Section</a>
and then in my controller, I set the url parameter this way
switch (option) {
case 'A': {
$scope.url = 'sectionA';
} break;
case 'B': {
$scope.url = 'sectionB';
} break;
}
Alternatively, I used directives to get it to work by generating the hyperlink with the desired ui-sref attribute according to the option selected on the select box (drop down).
Hhowever this means I have to re-create the link each time a different option is selected from the selectbox which causes an undesirable flickering effect.
My question is this, is it possible to change the value of the ui-sref as I tried doing above by simpling changing the value of url in my controller or do I have to re-create the entire element using a directive each time a selection is made as I have done below? (just showing this for completeness)
Select option directive (this directive generates the link directive)
define(['app/js/modules/app', 'app/js/directives/hyperLink'], function (app) {
app.directive('selectUsage', function ($compile) {
function createLink(scope,element) {
var newElm = angular.element('<hyper-link></hyper-link>');
var el = $(element).find('.navLink');
$(el).html(newElm);
$compile(newElm)(scope);
}
return {
restrict: 'E',
templateUrl: '/Client/app/templates/directives/select.html'
,link: function (scope, element, attrs) {
createLink(scope, element);
element.on('change', function () {
createLink(scope,element);
})
}
}
})
Hyperlink directive
define(['app/js/modules/app'], function (app) {
app.directive('hyperLink', function () {
return {
restrict: 'E',
templateUrl: '/Client/app/templates/directives/hyperLink.html',
link: function (scope, element, attrs) { }
}
})
Hyperlink template
<div>
<button ui-sref="form.{url}}">Next Section</button>
</div>
Looks like this is possible to do after all.
A breadcrumb on GitHub by one of the ui-router authors led me to try the following:
<a ng-href="{{getLinkUrl()}}">Dynamic Link</a>
Then, in your controller:
$scope.getLinkUrl = function(){
return $state.href('state-name', {someParam: $scope.someValue});
};
Turns out, this works like a charm w/ changing scoped values and all. You can even make the 'state-name' string constant reference a scoped value and that will update the href in the view as well :-)
There is a working plunker. The most easier way seems to be to use combination of:
$state.href() (doc here) and
ng-href (doc here)
These together could be used as:
<a ng-href="{{$state.href(myStateName, myParams)}}">
So, (following this plunker) having states like these:
$stateProvider
.state('home', {
url: "/home",
...
})
.state('parent', {
url: "/parent?param",
...
})
.state('parent.child', {
url: "/child",
...
We can change these values to dynamically generate the href
<input ng-model="myStateName" />
<input ng-model="myParams.param" />
Check it in action here
ORIGINAL:
There is a working example how to achieve what we need. But not with dynamic ui-sref .
As we can can check here: https://github.com/angular-ui/ui-router/issues/395
Q: [A]re dynamic ui-sref attributes not supported?
A: Correct.
But we can use different feature of ui-router: [$state.go("statename")][5]
So, this could be the controller stuff:
$scope.items = [
{label : 'first', url: 'first'},
{label : 'second', url: 'second'},
{label : 'third', url: 'third'},
];
$scope.selected = $scope.items[0];
$scope.gotoSelected = function(){
$state.go("form." + $scope.selected.url);
};
And here is the HTML template:
<div>
choose the url:
<select
ng-model="selected"
ng-options="item.label for item in items"
></select>
<pre>{{selected | json}}</pre>
<br />
go to selected
<button ng-click="gotoSelected()">here</button>
<hr />
<div ui-view=""></div>
</div>
The working example
NOTE: there is more up to date link to $state.go definition, but the deprecated one is a bit more clear to me
Take a look in this issue #2944.
The ui-sref doesn't watch the state expression, you can use ui-state and ui-state-params passing the variable.
<a data-ui-state="selectedState.state" data-ui-state-params="{'myParam':aMyParam}">
Link to page {{selectedState.name}} with myParam = {{aMyParam}}
</a>
Also a quickly demo provided in the ticket.
I managed to implement it this way (I'm using the controllerAs variant though - not via $scope).
Template
<button ui-sref="main({ i18n: '{{ ctrlAs.locale }}' })">Home</button>
Controller
var vm = this;
vm.locale = 'en'; // or whatever dynamic value you prepare
Also see the documentation to ui-sref where you can pass params:
https://github.com/angular-ui/ui-router/wiki/Quick-Reference#ui-sref
After trying various solutions I found the problem in the angular.ui.router code.
The problem comes from the fact that ui.router update method is triggered with the ref.state which means that it is not possible to update the value of the href used when the element is clicked.
Here are 2 solutions to solve the problem:
Custom Directive
module.directive('dynamicSref', function () {
return {
restrict: 'A',
scope: {
state: '#dynamicSref',
params: '=?dynamicSrefParams'
},
link: function ($scope, $element) {
var updateHref = function () {
if ($scope.state) {
var href = $rootScope.$state.href($scope.state, $scope.params);
$element.attr('href', href);
}
};
$scope.$watch('state', function (newValue, oldValue) {
if (newValue !== oldValue) {
updateHref();
}
});
$scope.$watch('params', function (newValue, oldValue) {
if (newValue !== oldValue) {
updateHref();
}
});
updateHref();
}
};
});
The HTML to use it is quite simple :
<a dynamic-sref="home.mystate"
dynamic-sref-params="{ param1 : scopeParam }">
Link
</a>
Fix ui.router code :
In angular.router.js your will find the directive $StateRefDirective (line 4238 for version 0.3).
Change the directive code to :
function $StateRefDirective($state, $timeout) {
return {
restrict: 'A',
require: ['?^uiSrefActive', '?^uiSrefActiveEq'],
link: function (scope, element, attrs, uiSrefActive) {
var ref = parseStateRef(attrs.uiSref, $state.current.name);
var def = { state: ref.state, href: null, params: null };
var type = getTypeInfo(element);
var active = uiSrefActive[1] || uiSrefActive[0];
var unlinkInfoFn = null;
var hookFn;
def.options = extend(defaultOpts(element, $state), attrs.uiSrefOpts ? scope.$eval(attrs.uiSrefOpts) : {});
var update = function (val) {
if (val) def.params = angular.copy(val);
def.href = $state.href(ref.state, def.params, def.options);
if (unlinkInfoFn) unlinkInfoFn();
if (active) unlinkInfoFn = active.$$addStateInfo(ref.state, def.params);
if (def.href !== null) attrs.$set(type.attr, def.href);
};
if (ref.paramExpr) {
scope.$watch(ref.paramExpr, function (val) { if (val !== def.params) update(val); }, true);
def.params = angular.copy(scope.$eval(ref.paramExpr));
}
// START CUSTOM CODE : Ability to have a 2 way binding on ui-sref directive
if (typeof attrs.uiSrefDynamic !== "undefined") {
attrs.$observe('uiSref', function (val) {
update(val);
if (val) {
var state = val.split('(')[0];
def.state = state;
$(element).attr('href', $state.href(def.state, def.params, def.options));
}
});
}
// END OF CUSTOM CODE
update();
if (!type.clickable) return;
hookFn = clickHook(element, $state, $timeout, type, function () { return def; });
element.bind("click", hookFn);
scope.$on('$destroy', function () {
element.unbind("click", hookFn);
});
}
};
}
Came to answer that for good :)
Fortunately, you don't need to use a button for ng-click, or use a function inside an ng-href to achieve what you seek. Instead;
You can create a $scope var in your controller and assign the ui-sref string in it and use it in your view, as ui-sref attribute.
Like this:
// Controller.js
// if you have nasted states, change the index [0] as needed.
// I'm getting the first level state after the root by index [0].
// You can get the child by index [1], and grandchild by [2]
// (if the current state is a child or grandchild, of course).
var page = $state.current.name.split('.')[0];
$scope.goToAddState = page + ".add";
// View.html
<a ui-sref="{{goToAddState}}">Add Button</a>
That works perfectly for me.
The best approach is to make use of uiRouter's $state.go('stateName', {params}) on button's ng-click directive. And disable the button if no option is selected.
HTML
<select ng-model="selected" ng-options="option as option.text for option in options"></select>
<button ng-disabled="!selected" type="button" ng-click="ctrl.next()">Next</button>
Controller
function Controller($scope, $state){
this.options = [{
text: 'Option One',
state: 'app.one',
params: {
param1: 'a',
param2: 'b'
}
},{
text: 'Option Two',
state: 'app.two',
params: {
param1: 'c',
param2: 'd'
}
},{
text: 'Option Three',
state: 'app.three',
params: {
param1: 'e',
param2: 'f'
}
}];
this.next = function(){
if(scope.selected){
$state.go($scope.selected.state, $scope.selected.params || {});
}
};
}
State
$stateProvider.state('wizard', {
url: '/wizard/:param1/:param2', // or '/wizard?param1&param2'
templateUrl: 'wizard.html',
controller: 'Controller as ctrl'
});
<a ng-click="{{getLinkUrl({someParam: someValue})}}">Dynamic Link</a>
$scope.getLinkUrl = function(value){
$state.go('My.State',{someParam:value});
}
It returns an object
this is just working for me
in controller
$scope.createState = 'stateName';
in view
ui-sref="{{ createState }}"
For manage multiple dynamic params using ui-sref, here my solution :
Html : ('MyPage.html')
<button type="button" ui-sref="myState(configParams())">
Controller : ('MyCtrl')
.controller('MyCtrl', function ($scope) {
$scope.params = {};
$scope.configParams = function() {
$scope.params.param1 = 'something';
$scope.params.param2 = 'oh again?';
$scope.params.param3 = 'yes more and more!';
//etc ...
return $scope.params;
};
}
stateProvider : ('myState')
$stateProvider
.state('myState', {
url: '/name/subname?param1&param2&param3',
templateUrl: 'MyPage.html',
controller: 'MyCtrl'
});
Enjoy !
<ul class="dropdown-menu">
<li ng-repeat="myPair in vm.Pairs track by $index">
<a ui-sref="buy({myPairIndex:$index})">
<span class="hidden-sm">{{myPair.pair}}</span>
</a>
</li>
</ul>
If someone only wants to dynamically set the $stateParams of ui-sref in Angularjs.
Note: In inspect element it will still appear as "buy({myPairIndex:$index})" but $index will be fetched in that state.
I found this solution the most appropriate:
.state('history', {
url: 'home',
controller: 'homeCtrl',
templateUrl: "home.html"
})
.state('settings', {
url: "/settings",
controller: 'settingsCtrl',
templateUrl: 'settings.html'
})
<button ui-sref="{{$ctrl.newProductLink()}}"</button>
ctrl.newProductLink = () => a > b ? 'home' : 'settings';
Or just something like this:
<a ui-sref="{{ condition ? 'stateA' : 'stateB'}}">
Link
</a>

AngularJS - scope watch is not triggered athough ng-class has been updated

I am trying to implement a dropdown mega-menu using Angular, where if the user clicks on Link 1, then the content for Link 1 should appear. My directives are as followed:
mobile-menu which acts as a controller and keeping track of the links and menu items states
menu-link, the actual link that user clicks on to open/close the menu items
menu-item, the menu item that should show/hide based on the scope.test value
Here is my AngularJS code:
angular.module("myApp", [])
.controller("testCtrl", function ($scope) {})
.directive("mobileMenu", function () {
return {
controller: function ($scope) {
this.menuLinks = [];
this.menuItems = [];
this.addMenuLink = function (l) {
this.menuLinks.push(l);
};
this.addMenuItem = function (m) {
this.menuItems.push(m);
};
// Function to close all other menu items if they are open.
// This is because only one menu item can be active at a time
this.closeOthers = function (selectedMenuLink) {
angular.forEach(this.menuLinks, function (l) {
if (l !== selectedMenuLink) {
l.selected = false;
}
});
angular.forEach(this.menuItems, function (m) {
if (selectedMenuLink.target == m.menuId) {
m.test = true;
} else {
m.test = false;
}
});
};
}
};
}).directive("menuLink", function () {
return {
require: "^mobileMenu",
scope: {},
link: function(scope, element, attrs, menuController) {
scope.selected = false;
menuController.addMenuLink(scope);
scope.$watch('selected', function(newValue, oldValue) {
if (oldValue === newValue) {return};
if (newValue) {
scope.target = angular.element(element[0].children[0]).attr("data-menu");
menuController.closeOthers(scope);
}
});
}
};
}).directive("menuItem", function () {
return {
require: "^mobileMenu",
scope: true,
link: function (scope, element, attrs, menuController) {
scope.test = false;
scope.menuId = attrs.id;
menuController.addMenuItem(scope);
scope.$watch('test', watchLink);
scope.$watch(attrs.collapse, watchLink);
scope.$watch(function () {
return scope.test;
}, watchLink);
var watchLink = function (newValue, oldValue) {
// Initializing for the first time, do nothing
if (newValue === oldValue) return;
// If the collapse attribute has a true value, collapse this element
if (newValue) {
collapse();
} else {
expand();
}
};
// Helper function to collapse the element
var collapse = function () {
element.css({
height: "0px"
});
};
// Helper function to show the element
var expand = function () {
element.css({
height: "200px"
});
};
}
};
});
And here is my HTML code:
<div ng-app="myApp" ng-controller="testCtrl">
<div mobile-menu>
<ul>
<li menu-link>
Link 1
</li>
<li menu-link>
Link 2
</li>
</ul>
<div id="menu0" ng-class="{'expanded' : test, 'collapsed' : !test}" menu-item collapse="!test">
<p class="text">First Menu</p>
</div>
<div id="menu1" ng-class="{'expanded' : test, 'collapsed' : !test}" menu-item collapse="!test">
<p class="promo-text">Second Menu</p>
</div>
</div>
I have an issue where if menu-link #1 is clicked, the corresponding menu-item #1's scope.test value should be updated and its' scope watch should be triggered, but it does not. If the scope watch triggered the watchLink function, then I would expect menu-item #1 would have a height of 200px.
I have also attached a jsfiddle example:
http://jsfiddle.net/EmwBP/28/
If you look at the browser console tool, the corresponding menu-item ng-class is always updated based on its scope.test value. However, even with my 3 different scope watchers set up, none of them were triggered. I am also using the ng-class directive just to show that the scope.test value does get updated and will be removed in the final implementation.
Normally, I would have put the menu-item directives as a child of menu-links, but I have a requirement where I have to put the menu-items as it is right now to achieve the slide-down effect of pushing elements below it down.
Many thanks in advance for your advices and assistance
You create a new isolated scope in menuLink by using scope: {}. So whatever scope variables you set inside that scope (like selected) are only available within that scope, and will not propagate to sibling or parent scopes.
What you need to do is de-isolate the menuLink scope by using scope: true.
That is not the problem. The problem is that watchLink is used before you define it. Fix where watchLink is triggered: http://jsfiddle.net/EmwBP/31/

How to prevent redirecting <a href="#something"> in Angular Js1.2

I was using AngularJs-1.0.7 and Bootstrap in my application. Recently I migrated from AngularJs-1.0.7 to AngularJs-1.2. I am using Bootstrap's Accordions and Tabs.
Html code for Tab contains <a href="#id_for_content"> as shown below.
<ul id="myTab" class="nav nav-tabs">
<li class="active">Home</li>
<li>Profile</li>
</ul>
<div id="myTabContent" class="tab-content">
<div class="tab-pane fade in active" id="firstTab">
<p>Content for first tab.</p>
</div>
<div class="tab-pane fade" id="secondTab">
<p>Content For second tab.</p>
</div>
</div>
In Angular's old versions, route change happens only if we give anchor tag like <a href="#/firstTab">. But AngularJs-1.2 redirects <a href="#firstTab">. It doesn't consider the / in between # and firstTab. Hence while clicking on Tab it redirects to http://web_url/#/firstTab . How to solve this issue?
My Solution
I found a solution for this issue. I wrote a directive for a tag. In that directive I checked for href attribute. If it matches prevent from its default behavior. Check the following code.
app.directive('a', function() {
return {
restrict: 'E',
link: function(scope, elem, attrs) {
if(attrs.href === '#firstTab'|| attrs.href === '#secondTab'){
elem.on('click', function(e){
e.preventDefault();
});
}
}
};
});
But the problem with this method is, I have to check each and every tab ids or accordion ids here. If I use dynamic Ids for them, its not possible to check in directive.
If you can find better solution, let us all know about it.
Just add this to your links:
target="_self"
And you're ready ;)
try data-target="#something", it works fine for me in both development and production environment.
I would just like to let you know that there is another solution to solve this issue (without defining function in controllers).
You can create a directive that will prevent default browser's behaviour (which is actually changing URL):
var PreventDefault = function () {
var linkFn = function (scope, element, attrs) {
$(element).on("click", function (event){
event.preventDefault();
});
};
return {
restrict: 'A',
link: linkFn
}
};
and then just add the directive to each a element responsible for toggling a tab like this:
<ul id="myTab" class="nav nav-tabs">
<li class="active">Home</li>
<li>Profile</li>
</ul>
<div id="myTabContent" class="tab-content">
<div class="tab-pane fade in active" id="firstTab">
<p>Content for first tab.</p>
</div>
<div class="tab-pane fade" id="secondTab">
<p>Content For second tab.</p>
</div>
</div>
In general you can define functions on the prototype of all scopes by using the rootscope. Add ng-click on your anchor tags and pass the $event to the rootscope handlers. Angular can send the originating event as well with ng-click:
<a ng-click="tabclick($event)">
in a run block of your module you can then set a tabClick function on $rootScope
$rootScope.tabclick = function ($event) { $event.preventDefault(); }
This will work for any scopes and you won't have to check against specific element ids.
Sample fiddle: http://jsfiddle.net/stto3703/wQe6P/
Cheers
You can use ng-click to call a function in your JS. In that function, use the event's preventDefault method, and use $location's hash method to change the hash in your URL.
I've been having this problem for a while now too, and I only just stumbled across this question (and your own answer). It turns out that you are almost correct with your solution, just a slight modification makes it work as you'd like.
In addition to the issue you were having with tabs, I was also encountering this problem with dropdown links -- they were navigating away from the current page when clicked, instead of dropping down the menu -- and in fact, this problem is present on any link that was designed to toggle something, i.e. with the data-toggle attribute set.
So, a slight modification to your code to check for the presence of the 'toggle' attribute solves it for me:
app.directive('a', function() {
return {
restrict: 'E',
link: function(scope, elem, attrs) {
if(attrs.toggle){
elem.on('click', function(e){
e.preventDefault();
});
}
}
};
});
Hope this helps!
Your solution in your question does work, but to prevent having to check for each anchor's id, change
if (attrs.href === '#firstTab'|| attrs.href === '#secondTab')
to
if (attrs.href && attrs.href.indexOf('#') > -1)
Directive:
.directive('a', function () {
return {
restrict: 'E',
link: function (scope, elem, attrs) {
if (attrs.href && attrs.href.indexOf('#') > -1) {
elem.on('click', function (e) {
e.preventDefault();
});
}
}
};
})
credits to
https://prerender.io/js-seo/angularjs-seo-get-your-site-indexed-and-to-the-top-of-the-search-results/
My solution:
- inside app.js try adding "$locationProvider.hashPrefix('!');"
.config(['$stateProvider', '$urlRouterProvider', '$httpProvider', '$locationProvider',
function($stateProvider, $urlRouterProvider, $httpProvider, $locationProvider) {
$stateProvider
.state('menu', {
url: '/',
cache: false,
abstract: true,
templateUrl: 'static/www/templates/menu.html'
})
.state('menu.main_page', {
url: 'app/main',
cache: false,
views: {
'menuContent': {
templateUrl: 'static/www/templates/mainpage.html',
controller: 'MainCtrl'
}
}
})
$locationProvider.hashPrefix('!');
$urlRouterProvider.otherwise(function ($injector, $location) {
var $state = $injector.get('$state');
$state.go('menu.mainpage');
});
}])
inside index.html try adding
<meta name="fragment" content="!">
and as Marcel said above
.directive('a', function () {
return {
restrict: 'E',
link: function (scope, elem, attrs) {
if (attrs.href && attrs.href.indexOf('#') > -1) {
elem.on('click', function (e) {
e.preventDefault();
});
}
}
};
})
that's all! works for me!
If you don't success with previous answers, you could try use $timeout... In this case I have used AngularUI tabs...
vm.removeUserTab = function (user) {
$timeout(function () {
vm.activeTab = 0;
var index = vm.userTabs.indexOf(user);
vm.userTabs.splice(index, 1);
});
};

In AngularJs, how do I identify a specific scope within a controller that is used multiple times on a page?

I have an application that uses multiple tabs with a grid in each tab. I have multiple pages using this same configuration.
I have a single controller that works for all grids. I would like to reuse this controller throughtout my application.
My problem is that I am trying to lazy load the data for each tab until the tab is clicked. What I can't figure out is how to tell the controller which scope belongs to which tab and ONLY get the data for that grid. I know internally AngularJs does this because if I just load all the data for each tab at once I can click on my paginator, search, etc. for each tab and only that scope is updated.
I have ng-click setup for each tab and I can get my controller to fire when a tab is clicked. However, the controller calls all instantiated scopes to load data for their respective grids.
My approach may not be the best but it seems silly to create seperate controllers that do exactly the same thing.
Note: Angular UI tabs with bootstrap is not an option.
My view
<div ng-app="TabsApp">
<div tabs>
<div class="tabs">
<a ng-click="clickTab(0)" ng-class="{selected: selectedTab==0}">Localized Text From Server</a>
<a ng-click="clickTab(1)" ng-class="{selected: selectedTab==1}">Localized Text From Server</a>
<a ng-click="clickTab(2)" ng-class="{selected: selectedTab==2}">Localized Text From Server</a>
</div>
<div class="tab-content">
<div ng-show="selectedTab==0" ng-init="init(#Model.UserId, 'useraddresses')" ng-controller="ListCtrl">#Html.Partial("_Grid0")</div>
<div ng-show="selectedTab==1" ng-init="init(#Model.UserId, 'userphones')" ng-controller="ListCtrl">#Html.Partial("_Grid1")</div>
<div ng-show="selectedTab==2" ng-init="init(#Model.UserId, 'usernotes')" ng-controller="ListCtrl">#Html.Partial("_Grid2")</div>
</div>
</div>
</div>
My app and factory url
var TabsApp = angular.module("TabsApp", ['ngResource', 'ngRoute']);
TabsApp.factory('Api', function($resource){
return $resource('/api/user/:userId/:ctrl', { userId: '#userId', ctrl: '#ctrl'});
});
My controller - child scope(s)
var ListCtrl = function ($scope, $location, Api){
$scope.search = function () {
Api.get({
userId: $scope.userId,
ctrl: $scope.ctrl,
q: $scope.query
//etc.
},
function(data){
$scope.items = data.items;
//other mapping
});
};
$scope.init = function(userId, ctrl){
$scope.userId = userId;
$scope.ctrl = ctrl;
};
$scope.reset = function(){
$scope.items = [];
$scope.search();
};
//kind of a hack but broadcasting was the only way I could figure out to listen for tab changes
$scope.tabModelChange = { 'isChanged': false };
$scope.$on('tabModelChange', function(event, args) {
$scope.tabModelChange.isChanged = true;
var activeTab = args.val[$scope.selectedTab];
if (!activeTab.isLoaded) {
$scope.reset();
}
});
//filtering, sorting, pagination, etc.
};
My directive: Parent scope
TabsApp.directive('tabs', function () {
return {
controller: function ($scope, $element) {
$scope.selectedTab = 0;
$scope.tabModel = [];
//I use localized text and any number of tabs on my views from the server so the client wont know how many tabs each view will have
var tabs = angular.element($element.children()[1]).children();
angular.forEach(tabs, function (value, key) {
$scope.tabModel.push({'isLoaded' : false, 'isActive': false});
});
$scope.clickTab = function(tab) {
$scope.selectedTab = tab;
$scope.tabModel[$scope.selectedTab].isActive = true;
};
$scope.$watch('tabModel', function(newVal, oldVal) {
if (newVal !== oldVal) {
$scope.$broadcast('tabModelChange', { 'val': newVal });
}
}, true);
}
};
});
I suspect that when my controller receives a broadcast that the tab model has changed it calls $scope.reset with $scope being a parent scope which then traverses the child scopes looking for 'reset'. Because there are 3 instances of ListCtrl, parent scope finds 3 child scopes with 'reset'. Hence, all the data gets loaded at once.
How can I get $scope.reset to match the tab that was clicked? Thanks.
With minimal change to your existing code, here's a plunker that does what you want, I think.
http://plnkr.co/edit/TVwWQehgWJay7ngA8g6B?p=preview
Basically I made a second directive called tab that accepts an argument which is a function to evaluate when that tab is switched to.
TabsApp.directive('tab', function () {
return {
require: '^tabs',
link: function (scope, element, attrs, tabsCtrl) {
tabsCtrl.add({
'isLoaded' : false,
'isActive': false,
'changed': function () { scope.$eval(attrs.tab); }
});
}
};
});
Here's what we do (i'm a bit lazy and going to pull use our code), We load when tab is clicked.
Here's the tabs
<ul class='nav nav-pills'>
<li ng-class="{ active : mode == 'incomplete'}"><a tabindex="-1" href="#/incomplete" ng-click='mode="incomplete"'>Incomplete orders</span></a></li>
<li ng-class="{ active : mode == 'completed'}"><a tabindex="-1" href="#/completed" ng-click='mode="completed"'>Completed orders</a></li>
</ul>
We setup routes
.config(['$routeProvider', '$env', function ($routeProvider, $env) {
$routeProvider.when("/completed", {
templateUrl: $env.urlRoot + "content/partials/orders/completed.htm", controller: 'CompletedOrdersCtrl'
});
$routeProvider.when("/incomplete", {
templateUrl: $env.urlRoot + "content/partials/orders/incomplete.htm", controller: 'IncompleteOrdersCtrl'
});
$routeProvider.otherwise({
templateUrl: $env.urlRoot + "content/partials/orders/incomplete.htm", controller: 'IncompleteOrdersCtrl'
});
} ]);
We then have the two controllers IncompleteOrdersCtrl and CompleteOrdersCtrl that call the service to get the correct data. You could possibly consolidate into one controller with a parameter passed in the route config.
Hopefully this works for you
--dan

Resources