Dynamically set the value of ui-sref Angularjs - 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>

Related

Update href in AngularJS before navigating to the URL

In an AngularJS application I have the following code:
<a target="_blank" ng-href="{{someProperty.href}}" ng-click="someMethod($event)">Hello!</a>
Now, someMethod() and someProperty belong to the same service.
Initially, someProperty.href has a default value.
What I need to do is that when the user clicks on the link, some calculation is performed and someProperty.href gets a new value. This new value need to be reflected in the ng-href and the user should be redirected to that new href.
tried reconstructing it and it seems to work, clicking on the link opens a new tab with the new url.
https://plnkr.co/edit/gy4eIKn02uF0S8dLGNx2?p=preview
<a target="_blank" ng-href="{{someService.someProperty.href}}" ng-click="someService.someMethod()">
Hello!
<br/>
{{someService.someProperty.href}}
</a>
You can do it as like the below code
;(function(angular) {
angular.module('myApp.directives')
.directive('myExample', myExample);
myExample.$inject = ['$timeout'];
function myExample($timeout) {
return {
restrict: 'A',
scope: {
myExample: '&',
ngHref: '='
},
link: function(scope, element, attrs) {
element.on('click', function(event) {
event.preventDefault();
$timeout(function() {
scope.myExample();
scope.$apply();
var target = attrs.target || '_blank';
var url = scope.ngHref;
angular.element('')[0].click();
});
});
}
};
}
})(angular);
In Controller
;(function(angular) {
'use strict';
angular.module('myApp.controllers').controller('HomeController', HomeController);
HomeController.$inject = ['$scope'];
function HomeController($scope) {
$scope.url = 'http://yahoo.com';
$scope.someFunction = function() {
$scope.url = 'http://google.com';
};
}
})(angular);
In HTML You can use like
<div ng-controller="HomeController">
<a ng-href="url" my-example="someFunction()" target="_blank">Click me to redirect</a>
</div>
Here instead of ng-click I have used custom directive which simulates the ng-click but not as exactly as ng-click
If the parent scope function is async you change your directive and someFunction in controller as like below
#Directive
;(function(angular) {
angular.module('myApp.directives')
.directive('myExample', myExample);
myExample.$inject = ['$timeout'];
function myExample($timeout) {
return {
restrict: 'A',
scope: {
myExample: '&',
ngHref: '='
},
link: function(scope, element, attrs) {
element.on('click', function(event) {
event.preventDefault();
scope.myExample().then(function() {
$timeout(function() {
scope.$apply();
var target = attrs.target || '_blank';
var url = scope.ngHref;
angular.element('')[0].click();
});
});
});
}
};
}
})(angular);
#Controller
;(function(angular) {
'use strict';
angular.module('myApp.controllers').controller('HomeController', HomeController);
HomeController.$inject = ['$scope', '$q'];
function HomeController($scope, $q) {
$scope.url = 'http://yahoo.com';
$scope.someFunction = function() {
var deferred = $q.defer();
$scope.url = 'http://google.com';
deferred.resolve('');
return deferred.promise;
};
}
})(angular);
Here I just simulated the async, it may be your http call too
make sure the value in href tag is updated after you click on it. Try debugging the ng-click function. According to the official documentation:
Using AngularJS markup like {{hash}} in an href attribute will make the link go to the wrong URL if the user clicks it before AngularJS has a chance to replace the {{hash}} markup with its value. Until AngularJS replaces the markup the link will be broken and will most likely return a 404 error. The ngHref directive solves this problem.
In your case, i think the old link is not getting updated with the new values of the model. Hence, redirecting to old link.
Try calling a function on ui-sref or ng-href which will return the state name that you want to redirect. Something like this
html:
<a ui-href="{{GetUpdatedHref()}}" >Hello!</a>
controller.js
$scope.GetUpdatedHref = function(){
//perform your http call while it's being processed show a loader then finally return the desired state (page location)
return "main.home"
}
If this doesn't work for you use ng-click instead of ui-sref and then $state.go("main.home") inside function.
Hope this may resolve your problem.

Select Text in Textbox After Showing Input Element via Controller

I'm looking for an AngularJS-friendly way to show an input box and select the text within the input. I've tried using the controller to add the logic, this is what I have so far:
HTML ComparisonCenterProfiles.html
<div id="saved-profiles" class="select-list" ng-cloak>
<ul>
<li ng-repeat="profile in profiles" ng-class="{'ellipsis': true, 'active': isActive(profile.CompSetId)}">
<span class="save-disk" ng-click="saveActiveProfile()" ng-show="isActive(profile.CompSetId) && profile.IsDirty" style="cursor: pointer;"></span>
{{profile.CompSetName}}
<input type="text" ng-model="profile.CompSetName" ng-show="forceRenameProfile && isActive(profile.CompSetId)" ng-blur="saveActiveProfile()" />
</li>
</ul>
</div>
Javascript
angular
.module('app')
.directive('compProfiles', ['pageMethod', function (pageMethod) {
return {
restrict: 'E',
require: '^compEditor',
controller: ['$scope', function ($scope) {
$scope.saveActiveProfile = function () {
if ($scope.activeProfile.CompSetName.length < 3) {
showAlert('Error',
'<strong>' + $scope.activeProfile.CompSetName + '</strong> is not a valid profile name. Your profile name must have at least 3 characters.',
[
{
text: 'OK',
click: function () {
$scope.forceRenameProfile = true;
hideAlert($(this));
}
}
]);
}
else {
continueSavingProfile();
$scope.forceRenameProfile = false;
}
}
}],
templateUrl: 'Templates/ComparisonCenterProfiles.html'
};
}]);
So at this point, I'm able to show the input box but have been unable to actually select the text within the input box in order to emphasize that the user needs to change the contents within the box.
Sidenote: I'm newer to AngularJS and, coming from a jQuery background, I of course tried using focus() and select() on the element to no avail.
Any ideas would be appreciated!
You can get the input element from link directive. Add this function into directive:
link: function(scope, element) {
var inputElement = element.find('input');
scope.selectText = function() {
inputElement.setSelectionRange(0, inputElement.value.length)
}
}
And in your controller you can call selectText function :
scope.selectText();
Thanks for your help Silvinus... I used a combination of your answer (using the link instead of the controller and using .setSelectionRange()) along with How to set focus on input field? post to come up with this final solution:
HTML
<input type="text" ng-show="forceRenameProfile && isActive(profile.CompSetId)" focus-me="forceRenameProfile && isActive(profile.CompSetId)" ng-model="profile.CompSetName" ng-blur="saveActiveProfile()" />
Javascript
.directive('focusMe', function ($timeout, $parse) {
return {
link: function (scope, element, attrs) {
var model = $parse(attrs.focusMe);
scope.$watch(model, function (value) {
if (value) {
$timeout(function () {
element[0].focus();
element[0].setSelectionRange(0, element[0].value.length);
});
}
});
}
};
});
This solution allowed me to use the same conditional for showing as for focusing and selecting.

Angularjs pass ui-router controller $scope to data

i want to pass a pageTitle data from ui-router based on a $stateParams and then update the title of the page using a directive
ui-router example:
.state('index.something.detail', {
url: '/{type}/{else}',
views: {
'': {
templateUrl: 'tempalte.html',
controller: function($scope, $stateParams) {
if ($stateParams.type == "param") {
$scope.title = "param";
}
}
}
},
data: {
pageTitle: 'Something {{title}}', //NOT WORKING
},
ncyBreadcrumb: {
label: 'Sometgin {{title}}', //IT WORKS
parent: 'index.something'
}
})
directive example:
function pageTitle($rootScope, $timeout) {
return {
link: function(scope, element) {
var listener = function(event, toState, toParams, fromState, fromParams) {
// Default title - load on Dashboard 1
var title = 'APP';
// Create your own title pattern
if (toState.data && toState.data.pageTitle) title = 'APP | ' + toState.data.pageTitle;
$timeout(function() {
element.text(title);
});
};
$rootScope.$on('$stateChangeStart', listener);
}
}
};
angular-breadcrumb pass the data ok
Something param
but in the pageTitle directive i get
Something {{title}}
how can i pass the $scope of the ui-router state?
Thanks!
We cannot pass this via data setting. The reason is, that the string param in data {} is just a string, not a view template. We would need to compile it and pass a scope.
But there is a better solution. Check this working example
Let's have index.html like this:
<H1><ui-view name="title" /></H1>
<ul>
<li><a ui-sref="parent">parent</a>
<li><a ui-sref="parent.child">parent.child</a>
</ul>
<div ui-view=""></div>
And our state can now inject stuff into two locations:
.state('state name', {
...
views : {
"" : {
// default view
}
"title" : {
template : "parent is setting title inside of index.html"
}
}
})
Check this here in action
Also, observe the UI-Router default sample application (doing the same trick) here:
http://angular-ui.github.io/ui-router/sample/#/
The most important part of the code is Contact state def, there is a small snippet:
// You can have unlimited children within a state. Here is a second child
// state within the 'contacts' parent state.
.state('contacts.detail', {
...
views: {
// So this one is targeting the unnamed view within the parent state's template.
'': {
...
},
// This one is targeting the ui-view="hint" within the unnamed root, aka index.html.
// This shows off how you could populate *any* view within *any* ancestor state.
'hint#': {
template: 'This is contacts.detail populating the "hint" ui-view'
},
// This one is targeting the ui-view="menuTip" within the parent state's template.
'menuTip': {

AngularJS : Getting ng-repeat element (template compiled) to manipulate in directive

The HTML:
<div ng-controller="AccordionCtrl">
<ul id="myAccordion">
<li class="bgDark main-tab-animation" ng-repeat="tab in accordionTabs"
ng-class="{'first-child' : $first, 'trend' : $last}">
<accordion-content data="tab"></accordion-content>
</li>
</ul>
</div>
My Controller:
angular.module('myApp')
.controller('AccordionCtrl', function ($scope) {
$scope.accordionTabs = [{type:'tab'},{type:'tab'},{type: 'tab'},{type:'tab'},{type:'tab'},{type:'tab'},{type:'trend'}];
});
My Directive:
angular.module('myApp')
.directive('accordionContent', function ($compile, $http, $templateCache) {
var getTemplate = function (contentType) {
var templateLoader, baseUrl = 'views/',
templateMap = { tab: 'tab1.html', tab2: 'tab2.html'};
var templateUrl = baseUrl + templateMap[contentType];
templateLoader = $http.get(templateUrl, {cache: $templateCache});
return templateLoader;
};
return {
replace: true,
restrict: 'E',
scope: {
data: '='
},
link: function (scope, element, attrs) {
var loader = getTemplate(scope.data.type);
var promise = loader.success(function(html) {
element.html(html);
}).then(function (response) {
element.replaceWith($compile(element.html())(scope));
});
element.addClass('juhee');
}
};
});
In the directive Iam loading 2 different templates (for now the templates are static html).
Can someone explain me how can I manipulate the DOM e.g. adding a class to the repeating li element in the directive. Now I've solved this with ng-class. But I would like donig this in the directive, e.g. like this way ....
if(scope.$last){
element.addClass("trend");
}
but it looks like the li elements from the ng-repeat are not rendered at the time Iam trying to manipulate them...
The log looks like this:
[accordion-content.ng-isolate-scope, ready: function, toString: function, eq: function, push: function, sort: function…]
How can I get the compiled or rendered element in the Directive to manipulate it e.g. adding removing classes or setting css .... ?
Thxs for your time and help!
vladi

How to highlight a current menu item?

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>

Resources