Skip to content
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 · 80 comments · Fixed by #2363
Closed

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

blah238 opened this issue Oct 7, 2014 · 80 comments · Fixed by #2363

Comments

@blah238
Copy link
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
Copy link

     <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
Copy link
Contributor Author

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
Copy link
Contributor Author

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
Copy link
Contributor Author

blah238 commented Oct 11, 2014

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

@SerhiiKozachenko
Copy link

+1

@dcolley
Copy link

dcolley commented Dec 1, 2014

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

@epelc
Copy link

epelc commented Dec 8, 2014

+1 this would help alot

@adamalbrecht
Copy link

+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
Copy link

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
Copy link

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.

@SerhiiKozachenko
Copy link

@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
Copy link

tagazok commented Dec 11, 2014

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

@squadwuschel
Copy link

+1 Same Problem

@nealtovsen
Copy link

+1

@rweng
Copy link

rweng commented Jan 14, 2015

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

@tomwganem
Copy link

+1

@neaped
Copy link

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
Copy link

lucky7id commented Feb 2, 2015

👍 +1

@f2net
Copy link

f2net commented Feb 10, 2015

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

@tristanz
Copy link

+1

1 similar comment
@ghoullier
Copy link

👍

@jaman1020
Copy link

+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
Copy link

+1

@ruchevits
Copy link

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
Copy link

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

@hdaws
Copy link

hdaws commented 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.

@MichaelJCole
Copy link

+1 @blah238 thanks for a simple solution.

@uguryilmaz
Copy link

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
Copy link

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

@nateabele
Copy link
Contributor

@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
Copy link

+1

@andersonef
Copy link

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

@Giovancruz
Copy link

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
Copy link

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.

@brendanluna
Copy link

+1 @arkin

1 similar comment
@basilinjoe
Copy link

+1 @arkin

@riteshjagga
Copy link

@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
Copy link

@adamalbrecht you are a saint, thanks

@axul
Copy link

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
Copy link

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
Copy link

@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 pushed a commit to ExpFront/ui-router that referenced this issue Jun 23, 2016
@samuil4
Copy link

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
Copy link

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
    Copy link

    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
    Copy link

    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
    Copy link

    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
    Copy link

    @arkin- works perfectly! Thank you.

    @nateabele
    Copy link
    Contributor

    @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
    Copy link

    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
    Copy link

    owenXin commented Dec 18, 2016

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

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

    @marioleed
    Copy link

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

    @oscarr-reyes
    Copy link

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

    @cristianmeza
    Copy link

    @owenXin Thank you very much, it worked perfect.

    @prashant-pokhriyal
    Copy link

    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>
    

    @tgrant59, how to dynamically set the ui-sref-active attribute. Actually I'm having it inside ng-repeat.

            <li ng-repeat="menu in top_nav_bar.menu_options" class="{{ menu.class}}"
              ui-sref="{{menu.sref}}" ui-sref-active="{'active': menu.sref + '.**'}">
              <a class="text-capitalize">{{ menu.name }}</a>
            </li>

    But ui-sref-active="{'active': menu.sref + '.**'}" is not working.

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    None yet
    Projects
    None yet
    Development

    Successfully merging a pull request may close this issue.