New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ui-sref-active not working correctly with abstract and child states #1431

Closed
blah238 opened this Issue Oct 7, 2014 · 79 comments

Comments

Projects
None yet
@blah238
Contributor

blah238 commented Oct 7, 2014

http://plnkr.co/edit/c7OS1pwI5IjAAs5cEi5s?p=preview

In the example above, clicking the Administration nav element shows a view with two tabs, Users and Roles.

These correspond to the states admin.users and admin.roles, while admin is an abstract state whose URL is inherited by admin.users. This all works fine except when setting the active class using ui-sref-active.

There are two problems in this scenario:

  1. The Administration nav element is not made active when on the Roles tab
  2. When refreshing the page (be sure to pop out the Plunker preview) while on the Roles tab, no elements get the active class set

I would like to avoid using $state.includes directly since ui-sref-active is supposed to work on child states since 0.2.11: #927

Am I doing something wrong or is this a bug?

@stoykostanchev

This comment has been minimized.

Show comment
Hide comment
@stoykostanchev

stoykostanchev Oct 8, 2014

     <li ui-sref-active="active"><a ui-sref="admin.users">Administration Panel</a>

The active class 'ui-sref-active' activates upon is not the abstract 'admin', but the 'admin.users' :?
I guess the way this is suppose to be done is - making the admin non-abstract, and setting the url of the default child state to "" (however, THAT does not work for me for some reason, trying to figure out right now if it's me or an issue)
Switching to urls based navigation would work, but seems to me not to be the right intent

     <li ui-sref-active="active"><a ui-sref="admin.users">Administration Panel</a>

The active class 'ui-sref-active' activates upon is not the abstract 'admin', but the 'admin.users' :?
I guess the way this is suppose to be done is - making the admin non-abstract, and setting the url of the default child state to "" (however, THAT does not work for me for some reason, trying to figure out right now if it's me or an issue)
Switching to urls based navigation would work, but seems to me not to be the right intent

@blah238

This comment has been minimized.

Show comment
Hide comment
@blah238

blah238 Oct 8, 2014

Contributor

@stoykostanchev Making admin non-abstract fixes the first problem, but not the second, and unfortunately adds a new one: the users tab is made active when it's not. See plunker: http://plnkr.co/edit/swUNUcvBE1DaITgWSWtD?p=preview

I feel like this really should be working the way it is, and that it's not is a bug.

Contributor

blah238 commented Oct 8, 2014

@stoykostanchev Making admin non-abstract fixes the first problem, but not the second, and unfortunately adds a new one: the users tab is made active when it's not. See plunker: http://plnkr.co/edit/swUNUcvBE1DaITgWSWtD?p=preview

I feel like this really should be working the way it is, and that it's not is a bug.

@blah238

This comment has been minimized.

Show comment
Hide comment
@blah238

blah238 Oct 8, 2014

Contributor

Well here's a workaround that fixes both issues for me: http://plnkr.co/edit/G5o6XkeCQfQId2XwUTXc?p=preview

This creates a flat object containing the the state names and their active status (true/false) into the root scope at startup, and updates it when $stateChangeSuccess fires.

I needed to do this instead of using $state.includes() directly because of issues with ui-bootstrap: angular-ui/bootstrap#1741 angular-ui/bootstrap#1539

If anyone has any better ideas I'm all ears. I wish these two libraries (ui-router and ui-bootstrap) integrated with each other better.

Contributor

blah238 commented Oct 8, 2014

Well here's a workaround that fixes both issues for me: http://plnkr.co/edit/G5o6XkeCQfQId2XwUTXc?p=preview

This creates a flat object containing the the state names and their active status (true/false) into the root scope at startup, and updates it when $stateChangeSuccess fires.

I needed to do this instead of using $state.includes() directly because of issues with ui-bootstrap: angular-ui/bootstrap#1741 angular-ui/bootstrap#1539

If anyone has any better ideas I'm all ears. I wish these two libraries (ui-router and ui-bootstrap) integrated with each other better.

@blah238

This comment has been minimized.

Show comment
Hide comment
@blah238

blah238 Oct 11, 2014

Contributor

@timkindberg could you take a look at this since it looks like you did the PR for this? Thanks!

Contributor

blah238 commented Oct 11, 2014

@timkindberg could you take a look at this since it looks like you did the PR for this? Thanks!

@Grievoushead

This comment has been minimized.

Show comment
Hide comment

+1

@dcolley

This comment has been minimized.

Show comment
Hide comment
@dcolley

dcolley Dec 1, 2014

+1 I can't get any classes to work in the partial/sidebar.html

dcolley commented Dec 1, 2014

+1 I can't get any classes to work in the partial/sidebar.html

@epelc

This comment has been minimized.

Show comment
Hide comment
@epelc

epelc Dec 8, 2014

+1 this would help alot

epelc commented Dec 8, 2014

+1 this would help alot

@adamalbrecht

This comment has been minimized.

Show comment
Hide comment
@adamalbrecht

adamalbrecht Dec 8, 2014

+1

I run into this issue a lot and, off the top of my head, here are a couple ideas for how it could be fixed:

First, you could add additional options to ui-sref-active so that you can specify a route rather than just using the route provided to ui-sref. Maybe something like this:

<a ui-sref='admin.users' ui-sref-active="{class: 'active', state: 'admin'}">Link</a>

Another way that might also be generally useful would be to allow linking to abstract states if it provides a default child state. Then you could do the following:

For example:

      // ...
      .state('admin', {
        url: '/admin',
        abstract: true,
        defaultChild: 'admin.users',
        templateUrl: 'templates/adminPanel.html'
      })
      .state('admin.users', {
        url: '',
        templateUrl: 'templates/adminUsers.html'
      })
      // ...
<a ui-sref="admin" ui-sref-active="active">Administration Panel</a>

+1

I run into this issue a lot and, off the top of my head, here are a couple ideas for how it could be fixed:

First, you could add additional options to ui-sref-active so that you can specify a route rather than just using the route provided to ui-sref. Maybe something like this:

<a ui-sref='admin.users' ui-sref-active="{class: 'active', state: 'admin'}">Link</a>

Another way that might also be generally useful would be to allow linking to abstract states if it provides a default child state. Then you could do the following:

For example:

      // ...
      .state('admin', {
        url: '/admin',
        abstract: true,
        defaultChild: 'admin.users',
        templateUrl: 'templates/adminPanel.html'
      })
      .state('admin.users', {
        url: '',
        templateUrl: 'templates/adminUsers.html'
      })
      // ...
<a ui-sref="admin" ui-sref-active="active">Administration Panel</a>
@tagazok

This comment has been minimized.

Show comment
Hide comment
@tagazok

tagazok Dec 9, 2014

I just had the same problem.
In my app, I have an abstract 'users' state, and my menu link goes to users.list but then when selecting a user and going to users.details, I lost the ui-sref-active :/

I like the idea to have an object intp ui-sref-active to specify the associated state :)

tagazok commented Dec 9, 2014

I just had the same problem.
In my app, I have an abstract 'users' state, and my menu link goes to users.list but then when selecting a user and going to users.details, I lost the ui-sref-active :/

I like the idea to have an object intp ui-sref-active to specify the associated state :)

@epelc

This comment has been minimized.

Show comment
Hide comment
@epelc

epelc Dec 9, 2014

@tagazok If your having the problem with a list/detail setup one way to get around it is to make your details state a child of your list state. This worked very well for me but it requires a little extra logic depending on if your details requires a second api call or if you already have the data from your list. You end up with users as an abstract state and users.list along with users.list.details so if you link to users.list then go to users.list.details you ui-sref-active will still work.

My problem is I have a settings state with multiple categories ie overview billing security teams states each with their own child states. So my above solution doesn't go very well with the above solution.

epelc commented Dec 9, 2014

@tagazok If your having the problem with a list/detail setup one way to get around it is to make your details state a child of your list state. This worked very well for me but it requires a little extra logic depending on if your details requires a second api call or if you already have the data from your list. You end up with users as an abstract state and users.list along with users.list.details so if you link to users.list then go to users.list.details you ui-sref-active will still work.

My problem is I have a settings state with multiple categories ie overview billing security teams states each with their own child states. So my above solution doesn't go very well with the above solution.

@Grievoushead

This comment has been minimized.

Show comment
Hide comment
@Grievoushead

Grievoushead Dec 9, 2014

@tagazok I have fixed this in my app by creating custom menu directive in which I have isActive func :

angular.module('Teach4Tech.Admin.Directives.Menu', [])
.directive('menu', [function(){
 return {
      restrict: 'A',
      template: '<li ng-repeat="item in menuItems" ng-class="{active: isActive(item.root)}"><a ui-sref="{{item.state}}"><span class="{{item.iconClass}}"></span>&nbsp;&nbsp;{{item.title}}</a></li>',
      controller: ['$scope', '$state', function ($scope, $state) {
        $scope.menuItems = [{
            title: 'Statistics',
              state: 'statistics',
              root: 'statistics',
            iconClass: 'glyphicon glyphicon-stats'
          }, {
              title: 'Videos',
                state: 'videos.list',
                root: 'videos',
            iconClass: 'glyphicon glyphicon-film'
          }, {
              title: 'Articles',
                state: 'articles',
                root: 'articles',
            iconClass: 'glyphicon glyphicon-pencil'
          }, {
              title: 'Courses',
                state: 'courses',
                root: 'courses',
            iconClass: 'glyphicon glyphicon-list-alt'
          }, {
              title: 'Messages',
                state: 'messages',
                root: 'messages',
            iconClass: 'glyphicon glyphicon-comment'
        }];

        $scope.isActive = function(root) {
          return $state.includes(root);
        };
      }]
    };
}]);

@tagazok I have fixed this in my app by creating custom menu directive in which I have isActive func :

angular.module('Teach4Tech.Admin.Directives.Menu', [])
.directive('menu', [function(){
 return {
      restrict: 'A',
      template: '<li ng-repeat="item in menuItems" ng-class="{active: isActive(item.root)}"><a ui-sref="{{item.state}}"><span class="{{item.iconClass}}"></span>&nbsp;&nbsp;{{item.title}}</a></li>',
      controller: ['$scope', '$state', function ($scope, $state) {
        $scope.menuItems = [{
            title: 'Statistics',
              state: 'statistics',
              root: 'statistics',
            iconClass: 'glyphicon glyphicon-stats'
          }, {
              title: 'Videos',
                state: 'videos.list',
                root: 'videos',
            iconClass: 'glyphicon glyphicon-film'
          }, {
              title: 'Articles',
                state: 'articles',
                root: 'articles',
            iconClass: 'glyphicon glyphicon-pencil'
          }, {
              title: 'Courses',
                state: 'courses',
                root: 'courses',
            iconClass: 'glyphicon glyphicon-list-alt'
          }, {
              title: 'Messages',
                state: 'messages',
                root: 'messages',
            iconClass: 'glyphicon glyphicon-comment'
        }];

        $scope.isActive = function(root) {
          return $state.includes(root);
        };
      }]
    };
}]);
@tagazok

This comment has been minimized.

Show comment
Hide comment
@tagazok

tagazok Dec 11, 2014

thanks @Grievoushead this is exactly what I needed ! :)

tagazok commented Dec 11, 2014

thanks @Grievoushead this is exactly what I needed ! :)

@squadwuschel

This comment has been minimized.

Show comment
Hide comment
@squadwuschel

squadwuschel Dec 16, 2014

+1 Same Problem

+1 Same Problem

@nealtovsen

This comment has been minimized.

Show comment
Hide comment

+1

@rweng

This comment has been minimized.

Show comment
Hide comment
@rweng

rweng Jan 14, 2015

+1, also like the idea of linking to abstract state if it provides a default child

rweng commented Jan 14, 2015

+1, also like the idea of linking to abstract state if it provides a default child

@tomwganem

This comment has been minimized.

Show comment
Hide comment

+1

@neaped

This comment has been minimized.

Show comment
Hide comment
@neaped

neaped Jan 22, 2015

I have got the similar problem, what I have done:

Don't use abstract within state, coz ui-sref-active support nest child state, so I use $state.go('child state') within the controller to force the parent state to go to it's child. But this will bring up the other problem, parent state's active class will never gone :( this ui-sref-active-eq won't work coz I don't give child state specific url.

neaped commented Jan 22, 2015

I have got the similar problem, what I have done:

Don't use abstract within state, coz ui-sref-active support nest child state, so I use $state.go('child state') within the controller to force the parent state to go to it's child. But this will bring up the other problem, parent state's active class will never gone :( this ui-sref-active-eq won't work coz I don't give child state specific url.

@lucky7id

This comment has been minimized.

Show comment
Hide comment

lucky7id commented Feb 2, 2015

👍 +1

@f2net

This comment has been minimized.

Show comment
Hide comment
@f2net

f2net Feb 10, 2015

+1 for the proposed form: ui-sref-active="{class: 'active', state: 'admin'}"

f2net commented Feb 10, 2015

+1 for the proposed form: ui-sref-active="{class: 'active', state: 'admin'}"

@tristanz

This comment has been minimized.

Show comment
Hide comment

+1

@ghoullier

This comment has been minimized.

Show comment
Hide comment

👍

@jaman1020

This comment has been minimized.

Show comment
Hide comment
@jaman1020

jaman1020 Feb 19, 2015

+1 on this, but in the meantime I've worked out a somewhat hacky solution that may or may not work for your use cases.
ui-sref-active works with child states, but not 'sister' states, such as when you have two tabs. What I've done is converted the second tab to a child state, but changed the same views by using absolute view targeting.
So -

    .state('user.home.main', {
       views: {
             view1@user.home: {}
             view2@user.home: {}
        }
    }
    .state('user.home.main.secondTab'. { // - because its part of user.home.main it retains state
      views: {
             view1@user.home: {}
             view2@user.home: {}
        }
    }

Again, depending on what you use your states for, this may or may not work for you, but at least it lets you preserve active state natively without having to create a directive or anything else.

+1 on this, but in the meantime I've worked out a somewhat hacky solution that may or may not work for your use cases.
ui-sref-active works with child states, but not 'sister' states, such as when you have two tabs. What I've done is converted the second tab to a child state, but changed the same views by using absolute view targeting.
So -

    .state('user.home.main', {
       views: {
             view1@user.home: {}
             view2@user.home: {}
        }
    }
    .state('user.home.main.secondTab'. { // - because its part of user.home.main it retains state
      views: {
             view1@user.home: {}
             view2@user.home: {}
        }
    }

Again, depending on what you use your states for, this may or may not work for you, but at least it lets you preserve active state natively without having to create a directive or anything else.

@santyclaz

This comment has been minimized.

Show comment
Hide comment

+1

@ruchevits

This comment has been minimized.

Show comment
Hide comment
@ruchevits

ruchevits Mar 4, 2015

I use the following in my application:

$scope.menu = [
    {
        title: 'Home',
        state: 'home'
    },
    {
        title: 'Categories',
        state: 'category'
    },
    {
        title: 'Category One',
        state: 'category.one',
        root: 'category'
    },
    {
        title: 'Category Two',
        state: 'category.two',
        root: 'category'
    }
];
$scope.isActive = function(root) {
    return $state.includes(root);
};
<li ng-repeat="item in menu" ng-class="{active: isActive(item.root || item.state)}">
  <a ui-sref="{{item.state}}">{{item.title}}</a>
</li>

So, if root is not defined for a state, we just fall back to default behaviour.

I use the following in my application:

$scope.menu = [
    {
        title: 'Home',
        state: 'home'
    },
    {
        title: 'Categories',
        state: 'category'
    },
    {
        title: 'Category One',
        state: 'category.one',
        root: 'category'
    },
    {
        title: 'Category Two',
        state: 'category.two',
        root: 'category'
    }
];
$scope.isActive = function(root) {
    return $state.includes(root);
};
<li ng-repeat="item in menu" ng-class="{active: isActive(item.root || item.state)}">
  <a ui-sref="{{item.state}}">{{item.title}}</a>
</li>

So, if root is not defined for a state, we just fall back to default behaviour.

@almandot

This comment has been minimized.

Show comment
Hide comment
@almandot

almandot Mar 15, 2015

+1 for either specifying the state for ui-sref-active or linking to abstract states if a default child is set... or both!

+1 for either specifying the state for ui-sref-active or linking to abstract states if a default child is set... or both!

@SignalDancer

This comment has been minimized.

Show comment
Hide comment
@SignalDancer

SignalDancer Mar 17, 2015

+1 Just hit this need myself in an application where I need to be "active" on an abstract parent. Reordering my hierarchy has other large implications that I can't back out of in a timely fashion right now.

+1 Just hit this need myself in an application where I need to be "active" on an abstract parent. Reordering my hierarchy has other large implications that I can't back out of in a timely fashion right now.

@MichaelJCole

This comment has been minimized.

Show comment
Hide comment
@MichaelJCole

MichaelJCole Mar 24, 2015

+1 @blah238 thanks for a simple solution.

+1 @blah238 thanks for a simple solution.

@uguryilmaz

This comment has been minimized.

Show comment
Hide comment
@uguryilmaz

uguryilmaz May 22, 2015

hi, i have this exect problem. i have a sidebar menu with 3 level deep.

System
User and Security
Sample Form
General Definitions
Currency
Country

i use states for routing. when i click Country menu item. it shows country page as expected. Country menu item highlited. no problem. then, when i click currency menu item, it show currency page but menu collapsed up to root. i analysed currency menu item via console, it has active class. i think parent elements lose their states somehow. i tried to create a plunker. i know menu doesnt collapsed at the beginning but look at the code side. it is the same code i use in my current project.

http://plnkr.co/edit/rFzDMlEAvxqXJ8hDhORc?p=preview

here is some screenshot:

first i click Ülke (Country) item from menu. it works normally.
image

and then i clicked Para Birimi (Currency) item it shows currency index page but menu collapsed up to root.

image

hi, i have this exect problem. i have a sidebar menu with 3 level deep.

System
User and Security
Sample Form
General Definitions
Currency
Country

i use states for routing. when i click Country menu item. it shows country page as expected. Country menu item highlited. no problem. then, when i click currency menu item, it show currency page but menu collapsed up to root. i analysed currency menu item via console, it has active class. i think parent elements lose their states somehow. i tried to create a plunker. i know menu doesnt collapsed at the beginning but look at the code side. it is the same code i use in my current project.

http://plnkr.co/edit/rFzDMlEAvxqXJ8hDhORc?p=preview

here is some screenshot:

first i click Ülke (Country) item from menu. it works normally.
image

and then i clicked Para Birimi (Currency) item it shows currency index page but menu collapsed up to root.

image

@budkin76

This comment has been minimized.

Show comment
Hide comment
@budkin76

budkin76 May 27, 2015

+1. I'm running into issues as my parent state is abstract but I need it to be active for all its children.

+1. I'm running into issues as my parent state is abstract but I need it to be active for all its children.

@budkin76

This comment has been minimized.

Show comment
Hide comment
@budkin76

budkin76 May 27, 2015

Also, @blah238 thank you for that workaround! Using that for now.

Also, @blah238 thank you for that workaround! Using that for now.

@sharpmachine

This comment has been minimized.

Show comment
Hide comment
@sharpmachine

sharpmachine Jun 16, 2015

An easy workaround is just put your first view in the ui-view like so:

<!-- list.html -->
<div ui-view class="animated fadeIn">
  <toolbar></toolbar>
  <child-layout>
    <charge-card ng-if="vm.charges" ng-repeat="charge in vm.charges" dynamic-popover="vm.dynamicPopover" charge="charge" show-confirm="vm.showConfirm"></charge-card>
    <div ng-show="!vm.charges.length">There are no charges for {{$parent.$parent.vm.subject.basicInfo.firstName}}. Add charges by clicking the plus button.</div>
  </child-layout>
</div>

<!-- edit.html -->
<toolbar></toolbar>
<child-layout>
  <formly-form model="vm.charge" fields="vm.chargeForm">
    <button class="btn btn-primary btn-raised" ng-click="vm.updateCharge()">Save Changes</button>
    <button class="btn btn-inverse btn-inverse-danger" back-button>Cancel</button>
    <button class="btn btn-sm btn-danger pull-right" ng-click="vm.showConfirm($event)"><i class="fa fa-trash"></i></button>
  </formly-form>
</child-layout>

<!-- link to charges list -->
<li role="presentation" ui-sref-active="active"><a ui-sref="subject.charges({subjectId: subject.id})">Charges <paper-ripple fit></paper-ripple></a></li>
//Routing
// GET /subjects/1/charges
  .state('subject.charges', {
    url: '/charges',
    templateUrl: 'components/charges/list.html',
    controller: 'ChargesListCtrl',
    controllerAs: 'vm',
    resolve: {
      charges: function(Subjects, $stateParams) {
        return Subjects.one($stateParams.subjectId).getList('charges');
      },
      charge: function(Charges, $stateParams) {
        return Charges.one($stateParams.chargeId).get();
      }
    }
  })

  // GET /subjects/1/charges/1
  .state('subject.charges.edit', {
    url: '/:chargeId',
    templateUrl: 'components/charges/edit.html',
    controller: 'ChargeEditCtrl',
    controllerAs: 'vm',
    resolve: {
      charge: function(Charges, $stateParams) {
        return Charges.one($stateParams.chargeId).get();
      }
    }
  })

All your child views will still go through the original ui-view in the list.html file and your navigation actives will still be true whether you're on the parent or the child.

An easy workaround is just put your first view in the ui-view like so:

<!-- list.html -->
<div ui-view class="animated fadeIn">
  <toolbar></toolbar>
  <child-layout>
    <charge-card ng-if="vm.charges" ng-repeat="charge in vm.charges" dynamic-popover="vm.dynamicPopover" charge="charge" show-confirm="vm.showConfirm"></charge-card>
    <div ng-show="!vm.charges.length">There are no charges for {{$parent.$parent.vm.subject.basicInfo.firstName}}. Add charges by clicking the plus button.</div>
  </child-layout>
</div>

<!-- edit.html -->
<toolbar></toolbar>
<child-layout>
  <formly-form model="vm.charge" fields="vm.chargeForm">
    <button class="btn btn-primary btn-raised" ng-click="vm.updateCharge()">Save Changes</button>
    <button class="btn btn-inverse btn-inverse-danger" back-button>Cancel</button>
    <button class="btn btn-sm btn-danger pull-right" ng-click="vm.showConfirm($event)"><i class="fa fa-trash"></i></button>
  </formly-form>
</child-layout>

<!-- link to charges list -->
<li role="presentation" ui-sref-active="active"><a ui-sref="subject.charges({subjectId: subject.id})">Charges <paper-ripple fit></paper-ripple></a></li>
//Routing
// GET /subjects/1/charges
  .state('subject.charges', {
    url: '/charges',
    templateUrl: 'components/charges/list.html',
    controller: 'ChargesListCtrl',
    controllerAs: 'vm',
    resolve: {
      charges: function(Subjects, $stateParams) {
        return Subjects.one($stateParams.subjectId).getList('charges');
      },
      charge: function(Charges, $stateParams) {
        return Charges.one($stateParams.chargeId).get();
      }
    }
  })

  // GET /subjects/1/charges/1
  .state('subject.charges.edit', {
    url: '/:chargeId',
    templateUrl: 'components/charges/edit.html',
    controller: 'ChargeEditCtrl',
    controllerAs: 'vm',
    resolve: {
      charge: function(Charges, $stateParams) {
        return Charges.one($stateParams.chargeId).get();
      }
    }
  })

All your child views will still go through the original ui-view in the list.html file and your navigation actives will still be true whether you're on the parent or the child.

@nktssh

This comment has been minimized.

Show comment
Hide comment
@nktssh

nktssh Jun 19, 2015

+1. Also having thins issue

nktssh commented Jun 19, 2015

+1. Also having thins issue

@eric-norcross

This comment has been minimized.

Show comment
Hide comment
@eric-norcross

eric-norcross Jun 22, 2015

+1.

Building on @stoykostanchev's solution; it would be great if it could also support wildcards similar to $state.includes()

For instance:

<a ui-sref='admin.users' ui-sref-active="{class: 'active', state: 'admin.*'}">Link</a>

Which, I feel would be more clear than writing:

 <a ui-sref='admin.users' ng-class=" $state.includes('admin.*') ? 'active' : null">Link</a>

+1.

Building on @stoykostanchev's solution; it would be great if it could also support wildcards similar to $state.includes()

For instance:

<a ui-sref='admin.users' ui-sref-active="{class: 'active', state: 'admin.*'}">Link</a>

Which, I feel would be more clear than writing:

 <a ui-sref='admin.users' ng-class=" $state.includes('admin.*') ? 'active' : null">Link</a>
@simmu

This comment has been minimized.

Show comment
Hide comment
@simmu

simmu Jul 1, 2015

+1. I like @eric-norcross suggestion on the wildcards support. My temporary work around is to maintain the ui-sref pointing to the abstract state and use ng-click to disabled the default behavior and use $state.go to the default child view.

simmu commented Jul 1, 2015

+1. I like @eric-norcross suggestion on the wildcards support. My temporary work around is to maintain the ui-sref pointing to the abstract state and use ng-click to disabled the default behavior and use $state.go to the default child view.

@arkin-

This comment has been minimized.

Show comment
Hide comment
@arkin-

arkin- Jul 16, 2015

+1

I fixed this using a custom directive:

app.directive('uiSrefActiveIf', ['$state', function($state) {
    return {
        restrict: "A",
        controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
            var state = $attrs.uiSrefActiveIf;

            function update() {
                if ( $state.includes(state) || $state.is(state) ) {
                    $element.addClass("active");
                } else {
                    $element.removeClass("active");
                }
            }

            $scope.$on('$stateChangeSuccess', update);
            update();
        }]
    };
}])

Then you use matching HTML ui-sref-active-if="parent" will add the class "active"

arkin- commented Jul 16, 2015

+1

I fixed this using a custom directive:

app.directive('uiSrefActiveIf', ['$state', function($state) {
    return {
        restrict: "A",
        controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
            var state = $attrs.uiSrefActiveIf;

            function update() {
                if ( $state.includes(state) || $state.is(state) ) {
                    $element.addClass("active");
                } else {
                    $element.removeClass("active");
                }
            }

            $scope.$on('$stateChangeSuccess', update);
            update();
        }]
    };
}])

Then you use matching HTML ui-sref-active-if="parent" will add the class "active"

@ailling

This comment has been minimized.

Show comment
Hide comment
@ailling

ailling Jul 18, 2015

Thanks @arkin; works perfectly for me

ailling commented Jul 18, 2015

Thanks @arkin; works perfectly for me

@domalak

This comment has been minimized.

Show comment
Hide comment

domalak commented Jul 20, 2015

+1 to @arkin-

@jancel

This comment has been minimized.

Show comment
Hide comment
@jancel

jancel Aug 10, 2015

+1 @arkin Wonder if they could bring this in. Simple to integrate

@arkin You may want to add the $state injection.

...
['$scope', '$element', '$attrs', '$state', function ($scope, $element, $attrs, $state) {
...

jancel commented Aug 10, 2015

+1 @arkin Wonder if they could bring this in. Simple to integrate

@arkin You may want to add the $state injection.

...
['$scope', '$element', '$attrs', '$state', function ($scope, $element, $attrs, $state) {
...
@alonronin

This comment has been minimized.

Show comment
Hide comment
@alonronin

alonronin Aug 10, 2015

@jancel he injected it to the directive callback

@jancel he injected it to the directive callback

@arkin-

This comment has been minimized.

Show comment
Hide comment
@arkin-

arkin- Oct 23, 2015

Ok @nateabele I'm not following, do you have an example?

arkin- commented Oct 23, 2015

Ok @nateabele I'm not following, do you have an example?

@nateabele

This comment has been minimized.

Show comment
Hide comment
@nateabele

nateabele Oct 24, 2015

Member

@arkin- Something similar to the example @eric-norcross posted, i.e. ui-sref-active="{ activeClass: 'active', state: 'admin.*' }" — then you'd just need to patch uiSrefActive to check whether the value is an object or string, and act accordingly. That probably just means appending the named state to states, and no-op'ing $$addStateInfo().

Member

nateabele commented Oct 24, 2015

@arkin- Something similar to the example @eric-norcross posted, i.e. ui-sref-active="{ activeClass: 'active', state: 'admin.*' }" — then you'd just need to patch uiSrefActive to check whether the value is an object or string, and act accordingly. That probably just means appending the named state to states, and no-op'ing $$addStateInfo().

@zloidemon

This comment has been minimized.

Show comment
Hide comment

+1

@andersonef

This comment has been minimized.

Show comment
Hide comment
@andersonef

andersonef Nov 3, 2015

@Grievoushead Thank you so much... You solution were awesome and simple! Thanks again

@Grievoushead Thank you so much... You solution were awesome and simple! Thanks again

fpipita added a commit to fpipita/ui-router that referenced this issue Nov 4, 2015

fpipita added a commit to fpipita/ui-router that referenced this issue Nov 7, 2015

fpipita added a commit to fpipita/ui-router that referenced this issue Nov 7, 2015

fpipita added a commit to fpipita/ui-router that referenced this issue Nov 7, 2015

fpipita added a commit to fpipita/ui-router that referenced this issue Nov 7, 2015

fpipita added a commit to fpipita/ui-router that referenced this issue Nov 8, 2015

fpipita added a commit to fpipita/ui-router that referenced this issue Nov 8, 2015

fpipita added a commit to fpipita/ui-router that referenced this issue Nov 14, 2015

fpipita added a commit to fpipita/ui-router that referenced this issue Nov 14, 2015

@nateabele nateabele closed this in #2363 Nov 14, 2015

fpipita added a commit to fpipita/ui-router that referenced this issue Nov 17, 2015

@Giovancruz

This comment has been minimized.

Show comment
Hide comment
@Giovancruz

Giovancruz Dec 15, 2015

Sorry i am new on angular, I think I'm doing something wrong. The solution from @arkin- dont works for me. Anybody can help me?
http://plnkr.co/edit/JmrZwbEJUjN3iKcrhiVl?p=preview

Sorry i am new on angular, I think I'm doing something wrong. The solution from @arkin- dont works for me. Anybody can help me?
http://plnkr.co/edit/JmrZwbEJUjN3iKcrhiVl?p=preview

@jwuliger

This comment has been minimized.

Show comment
Hide comment
@jwuliger

jwuliger Jan 3, 2016

@Giovancruz and @arkin- This directive worked perfectly for me. Thanks you so much. I spent over 8 hours trying to find a working solution. I am sorry it is not working for you @Giovancruz.

jwuliger commented Jan 3, 2016

@Giovancruz and @arkin- This directive worked perfectly for me. Thanks you so much. I spent over 8 hours trying to find a working solution. I am sorry it is not working for you @Giovancruz.

@blunatic

This comment has been minimized.

Show comment
Hide comment

blunatic commented Mar 9, 2016

+1 @arkin

@basilinjoe

This comment has been minimized.

Show comment
Hide comment
@riteshjagga

This comment has been minimized.

Show comment
Hide comment
@riteshjagga

riteshjagga Apr 7, 2016

@Giovancruz
I'm new to plunkr so didn't know how to modify your code. I made following changes to make it working:

  • Changed parent from dashboard to dashboard.payments.
  • and added <div class="ui-view"></div> in the payments.html file.
.state('dashboard.payments.paymentdetail', {
        parent: 'dashboard.payments',
        url:'/detail',
        templateUrl: 'paymentdetail.html',
        //controller: 'PaymentdetailCtrl',
        //controllerAs: 'paymentDetail',
        ncyBreadcrumb: {
          label: 'BILLINGDETAILS',
          parent: 'dashboard.payments'
        },
      })

@Giovancruz
I'm new to plunkr so didn't know how to modify your code. I made following changes to make it working:

  • Changed parent from dashboard to dashboard.payments.
  • and added <div class="ui-view"></div> in the payments.html file.
.state('dashboard.payments.paymentdetail', {
        parent: 'dashboard.payments',
        url:'/detail',
        templateUrl: 'paymentdetail.html',
        //controller: 'PaymentdetailCtrl',
        //controllerAs: 'paymentDetail',
        ncyBreadcrumb: {
          label: 'BILLINGDETAILS',
          parent: 'dashboard.payments'
        },
      })
@MaximilianLloyd

This comment has been minimized.

Show comment
Hide comment
@MaximilianLloyd

MaximilianLloyd Apr 22, 2016

@adamalbrecht you are a saint, thanks

@adamalbrecht you are a saint, thanks

@axul

This comment has been minimized.

Show comment
Hide comment
@axul

axul May 19, 2016

I made a little change to @arkin directive

.directive('uiSrefActiveIf', ['$state', '$parse', function($state, $attrs, $parse) {
    return {
        restrict: "A",
        link: function(scope, element, attrs){
            var state = attrs.uiSrefActiveIf;   

            function update() {
                if ( $state.includes(state) || $state.is(state) ) {
                    element.addClass("active");
                } else {
                    element.removeClass("active");
                }
            }

            scope.$on('$stateChangeSuccess', update);
            update();
        }
    };
}])

All I did was to move the logic from the controller to the link function in the directive to be able to get the ui-sref-active-if attr from a scope instead of a literal string

axul commented May 19, 2016

I made a little change to @arkin directive

.directive('uiSrefActiveIf', ['$state', '$parse', function($state, $attrs, $parse) {
    return {
        restrict: "A",
        link: function(scope, element, attrs){
            var state = attrs.uiSrefActiveIf;   

            function update() {
                if ( $state.includes(state) || $state.is(state) ) {
                    element.addClass("active");
                } else {
                    element.removeClass("active");
                }
            }

            scope.$on('$stateChangeSuccess', update);
            update();
        }
    };
}])

All I did was to move the logic from the controller to the link function in the directive to be able to get the ui-sref-active-if attr from a scope instead of a literal string

@tgrant59

This comment has been minimized.

Show comment
Hide comment
@tgrant59

tgrant59 May 31, 2016

It seems like people are still looking at this even though the solution is in at least the most recent version!
It is still undocumented, but just use an object (like with ng-class) and it will work.

This won't work right:

<li ui-sref-active="active">
  <a ui-sref="admin.users">Administration Panel</a>
</li>

This will:

<li ui-sref-active="{ 'active': 'admin' }">
  <a ui-sref="admin.users">Administration Panel</a>
</li>

It seems like people are still looking at this even though the solution is in at least the most recent version!
It is still undocumented, but just use an object (like with ng-class) and it will work.

This won't work right:

<li ui-sref-active="active">
  <a ui-sref="admin.users">Administration Panel</a>
</li>

This will:

<li ui-sref-active="{ 'active': 'admin' }">
  <a ui-sref="admin.users">Administration Panel</a>
</li>
@eddoliveira

This comment has been minimized.

Show comment
Hide comment
@eddoliveira

eddoliveira Jun 3, 2016

@tgrant59 That worked. Thanks!

My example:

<span ui-sref-active="{'active':'details.info.medical'}"> <a href="" ui-sref="details.info.medical({param1: object1, param2: object2})"> <span>MEDICAL INFO</span> </a> </span>

@tgrant59 That worked. Thanks!

My example:

<span ui-sref-active="{'active':'details.info.medical'}"> <a href="" ui-sref="details.info.medical({param1: object1, param2: object2})"> <span>MEDICAL INFO</span> </a> </span>

ExpFront added a commit to ExpFront/ui-router that referenced this issue Jun 23, 2016

@samuil4

This comment has been minimized.

Show comment
Hide comment
@samuil4

samuil4 Jul 1, 2016

@tgrant59 Thanks for the example.

Notes: Angular developers are still workarounding integrated features like this one in this retarded framework. THE NEW INTEGRATED FEATURES ARE NOT DOCUMENTED AT ALL!!! Every developer must go first to the official documentation where he finds nothing useful, then he goes to stackoverflow where all solutions are outdated or bad practices, then he decides that he will either waste 2 more hours going in to the comments of a related issue and explore all pull request OR simply write a custom solution that in 100% of the cases simply overrides or workarounds core features of the framework.

Thanks to the google marketing team frameworks like angular are well sold to end clients without even thinking if this framework will be a major drawback for the entire project. And that's how zillions of govnocode projects are born.

PS: wasted 3 hours to find an example of this feature and to determine if this feature is actually built in to the framework...

samuil4 commented Jul 1, 2016

@tgrant59 Thanks for the example.

Notes: Angular developers are still workarounding integrated features like this one in this retarded framework. THE NEW INTEGRATED FEATURES ARE NOT DOCUMENTED AT ALL!!! Every developer must go first to the official documentation where he finds nothing useful, then he goes to stackoverflow where all solutions are outdated or bad practices, then he decides that he will either waste 2 more hours going in to the comments of a related issue and explore all pull request OR simply write a custom solution that in 100% of the cases simply overrides or workarounds core features of the framework.

Thanks to the google marketing team frameworks like angular are well sold to end clients without even thinking if this framework will be a major drawback for the entire project. And that's how zillions of govnocode projects are born.

PS: wasted 3 hours to find an example of this feature and to determine if this feature is actually built in to the framework...

@dayachand

This comment has been minimized.

Show comment
Hide comment
@dayachand

dayachand Jul 15, 2016

I solve third level navigation issue in homer theme for angular js.

css for this:
#side-menu li .nav-second-level li .nav-third-level li a{color:#6a6c6f; padding: 7px 10px 7px 53px;}
#side-menu li .nav-second-level li .nav-third-level li.active a{color:#3498db}

navigation for this:
here admin and admin-setup are base state

  • Admin Settings
    • Company Setup / Profile
    • Services Menu
    • Manage Users
    • Billing Items
    • Manage Category
      • Expense Category
      • Device Types
      • Designation Setup
      • Ticket Classes
  • I solve third level navigation issue in homer theme for angular js.

    css for this:
    #side-menu li .nav-second-level li .nav-third-level li a{color:#6a6c6f; padding: 7px 10px 7px 53px;}
    #side-menu li .nav-second-level li .nav-third-level li.active a{color:#3498db}

    navigation for this:
    here admin and admin-setup are base state

  • Admin Settings
    • Company Setup / Profile
    • Services Menu
    • Manage Users
    • Billing Items
    • Manage Category
      • Expense Category
      • Device Types
      • Designation Setup
      • Ticket Classes
  • @phazei

    This comment has been minimized.

    Show comment
    Hide comment
    @phazei

    phazei Aug 12, 2016

    This is great:

    <span ui-sref-active="{'active':'details.info.medical'}"> <a href="" ui-sref="details.info.medical({param1: object1, param2: object2})"> <span>MEDICAL INFO</span> </a> </span>
    

    But what if you have a menu item that has two abstract items under it?
    The code does a foreach but since the class is the key, you can't have two routes use the same class..
    eg) {'active':'details.info.medical', 'active':'details.records'}
    The unique thing here is going to be the state name, so that should be the key, or it should allow the value to be an array which wouldn't break BC.
    eg) {active':['details.info.medical','details.records']}

    current work around:
    {'active':'details.info.medical', 'active 1':'details.records'}

    phazei commented Aug 12, 2016

    This is great:

    <span ui-sref-active="{'active':'details.info.medical'}"> <a href="" ui-sref="details.info.medical({param1: object1, param2: object2})"> <span>MEDICAL INFO</span> </a> </span>
    

    But what if you have a menu item that has two abstract items under it?
    The code does a foreach but since the class is the key, you can't have two routes use the same class..
    eg) {'active':'details.info.medical', 'active':'details.records'}
    The unique thing here is going to be the state name, so that should be the key, or it should allow the value to be an array which wouldn't break BC.
    eg) {active':['details.info.medical','details.records']}

    current work around:
    {'active':'details.info.medical', 'active 1':'details.records'}

    @riteshjagga

    This comment has been minimized.

    Show comment
    Hide comment
    @riteshjagga

    riteshjagga Aug 17, 2016

    What if you have nested abstract states as in the following routes
    organizations.organization(abstract).list/add/edit etc. and organizations.organization(abstract).users(abstract).list/add/edit etc.

    but with a flattened menu like this:

    <a class="list-group-item"
          ui-sref="home.organizations.organization.view({orgId: loggedInUser.organizationId})"
          ui-sref-active="{'active': 'home.organizations.organization', 'deactive': 'home.organizations.organization.users'}">
          Organization
    </a>
    <a class="list-group-item"
          ui-sref="home.organizations.organization.users.list({orgId: loggedInUser.organizationId})"
          ui-sref-active="{'active': 'home.organizations.organization.users'}">
          Users
    </a>
    

    When you are within the Users routes, both the menu items will be highlighted.

    To prevent this, added another class 'deactive': 'home.organizations.organization.users' and this deactive class can have the styles as in normal state of the menu item.

    It might be helpful for someone.

    What if you have nested abstract states as in the following routes
    organizations.organization(abstract).list/add/edit etc. and organizations.organization(abstract).users(abstract).list/add/edit etc.

    but with a flattened menu like this:

    <a class="list-group-item"
          ui-sref="home.organizations.organization.view({orgId: loggedInUser.organizationId})"
          ui-sref-active="{'active': 'home.organizations.organization', 'deactive': 'home.organizations.organization.users'}">
          Organization
    </a>
    <a class="list-group-item"
          ui-sref="home.organizations.organization.users.list({orgId: loggedInUser.organizationId})"
          ui-sref-active="{'active': 'home.organizations.organization.users'}">
          Users
    </a>
    

    When you are within the Users routes, both the menu items will be highlighted.

    To prevent this, added another class 'deactive': 'home.organizations.organization.users' and this deactive class can have the styles as in normal state of the menu item.

    It might be helpful for someone.

    @sarahsga

    This comment has been minimized.

    Show comment
    Hide comment
    @sarahsga

    sarahsga Aug 20, 2016

    I have solved it by overriding ui-sref-active ( well, not exactly):

    angular.module('app.layout')
        .directive('uiSrefActive2', StateRefActiveDirective);
    
      StateRefActiveDirective.$inject = ['$state', '$stateParams', '$interpolate'];
    
      function StateRefActiveDirective($state, $stateParams, $interpolate) {
        return {
          restrict: "A",
          priority: 1,
          controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
            var state, params, activeClassAndStateStr;
    
            activeClassAndStateStr = $interpolate($attrs.uiSrefActiveEq || $attrs.uiSrefActive2 || '', false)($scope);
            var jsonString = activeClassAndStateStr.replace(/'/g, '"');
            var activeClassAndStateObj = JSON.parse(jsonString);
    
    
            $scope.$on('$stateChangeSuccess', update);
    
            // Update route state
            function update(event, toState, toParams, fromState, fromParams, options) {
    
              if (toState.name.startsWith(activeClassAndStateObj.state)) {
                $element.addClass(activeClassAndStateObj.class);
              } else {
                $element.removeClass(activeClassAndStateObj.class);
              }
    
            };
    
          }]
        }
      }
    

    and then using this directive in the view:

    <ion-item ui-sref="app.parent.child" ui-sref-active2="{'class':'active','state':'app.parent'}"> </ion-item>
    

    sarahsga commented Aug 20, 2016

    I have solved it by overriding ui-sref-active ( well, not exactly):

    angular.module('app.layout')
        .directive('uiSrefActive2', StateRefActiveDirective);
    
      StateRefActiveDirective.$inject = ['$state', '$stateParams', '$interpolate'];
    
      function StateRefActiveDirective($state, $stateParams, $interpolate) {
        return {
          restrict: "A",
          priority: 1,
          controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
            var state, params, activeClassAndStateStr;
    
            activeClassAndStateStr = $interpolate($attrs.uiSrefActiveEq || $attrs.uiSrefActive2 || '', false)($scope);
            var jsonString = activeClassAndStateStr.replace(/'/g, '"');
            var activeClassAndStateObj = JSON.parse(jsonString);
    
    
            $scope.$on('$stateChangeSuccess', update);
    
            // Update route state
            function update(event, toState, toParams, fromState, fromParams, options) {
    
              if (toState.name.startsWith(activeClassAndStateObj.state)) {
                $element.addClass(activeClassAndStateObj.class);
              } else {
                $element.removeClass(activeClassAndStateObj.class);
              }
    
            };
    
          }]
        }
      }
    

    and then using this directive in the view:

    <ion-item ui-sref="app.parent.child" ui-sref-active2="{'class':'active','state':'app.parent'}"> </ion-item>
    
    @ffradegrada

    This comment has been minimized.

    Show comment
    Hide comment
    @ffradegrada

    ffradegrada Sep 13, 2016

    @arkin- works perfectly! Thank you.

    @arkin- works perfectly! Thank you.

    @nateabele

    This comment has been minimized.

    Show comment
    Hide comment
    @nateabele

    nateabele Sep 13, 2016

    Member

    @samuil4 Nobody on the UI Router project works for Google. We all do this in our spare time. If you don't like the situation, figure out the issue and submit a patch to the documentation. If everyone did that just once, you wouldn't need StackOverflow.

    Member

    nateabele commented Sep 13, 2016

    @samuil4 Nobody on the UI Router project works for Google. We all do this in our spare time. If you don't like the situation, figure out the issue and submit a patch to the documentation. If everyone did that just once, you wouldn't need StackOverflow.

    @arshabh

    This comment has been minimized.

    Show comment
    Hide comment
    @arshabh

    arshabh Oct 9, 2016

    we should have the default state for the abstract state - just like react router has index route - that will solve many problems.

    arshabh commented Oct 9, 2016

    we should have the default state for the abstract state - just like react router has index route - that will solve many problems.

    @owenXin

    This comment has been minimized.

    Show comment
    Hide comment
    @owenXin

    owenXin Dec 18, 2016

    Finally I got a solution from the following issue:
    #2954

    ui-sref-active="{'active': 'administration.**'}"
    

    owenXin commented Dec 18, 2016

    Finally I got a solution from the following issue:
    #2954

    ui-sref-active="{'active': 'administration.**'}"
    
    @marioleed

    This comment has been minimized.

    Show comment
    Hide comment
    @marioleed

    marioleed Jan 5, 2017

    Nice @owenXin! @christopherthielen's solution did it for me as well.

    Nice @owenXin! @christopherthielen's solution did it for me as well.

    @oscarr-reyes

    This comment has been minimized.

    Show comment
    Hide comment
    @oscarr-reyes

    oscarr-reyes Oct 16, 2017

    This doesn't work in ui-sref-active-eq

    This doesn't work in ui-sref-active-eq

    @cristianmeza

    This comment has been minimized.

    Show comment
    Hide comment
    @cristianmeza

    cristianmeza Nov 25, 2017

    @owenXin Thank you very much, it worked perfect.

    @owenXin Thank you very much, it worked perfect.

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment