77

Consider the following:

.state('manager.staffList', {url:'^/staff?alpha', templateUrl: 'views/staff.list.html', data:{activeMenu: 'staff'}, controller: 'staffListCtrl'})
.state('manager.staffDetail', {url:'^/staff/{id}' , templateUrl: 'views/staff.html', data:{activeMenu: 'staff'}, controller: 'staffDetailsCtrl'})
  .state('manager.staffDetail.view', {url:'/view',  templateUrl: 'views/staff.details.html', data:{activeMenu: 'staff'}})
    .state('manager.staffDetail.view.schedule', {url:'/schedule', templateUrl:'views/staff.view.schedule.html', data:{activeMenu: 'staff'}})
    .state('manager.staffDetail.view.history', {url:'/history' , templateUrl:'views/staff.view.history.html', data:{activeMenu: 'staff'}})
    .state('manager.staffDetail.view.log', {url:'/log', templateUrl:'views/staff.view.log.html', data:{activeMenu: 'staff'}})
    .state('manager.staffDetail.view.files', {url:'/files', templateUrl:'views/staff.view.files.html', data:{activeMenu: 'staff'}})
  .state('manager.staffDetail.edit', {url:'/edit',  templateUrl: 'views/staff.edit.html', data:{activeMenu: 'staff'}})

If I go to example.com/staff/1234/view, how do I default to the manager.staffDetail.view.schedule child state?

Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
Meeker
  • 5,979
  • 2
  • 20
  • 38
  • FYI, I currently use the `redirectTo` method found here http://stackoverflow.com/questions/29491079/redirect-a-state-to-default-substate-with-ui-router-in-angularjs – Meeker Oct 30 '15 at 15:25
  • Please do NOT edit this to create more vertical code. The code is formatted specifically to visually show the child states in comparison to their parents. – Meeker Mar 17 '16 at 13:15
  • @UdayHiwarale that is the same link already posted above – Meeker May 11 '16 at 16:24
  • My bad. I did not see. – Uday Hiwarale May 12 '16 at 04:17

9 Answers9

136
  1. First, add a property to the 'manager.staffDetail.view' state of abstract:true. This isn't required, but you want to set this since you'd never go to this state directly, you'd always go to one of it's child states.

  2. Then do one of the following:

  • Give the 'manager.staffDetail.view.schedule' state an empty URL. This will make it match the same URL as it's parent state URL, because it appends nothing to the parent URL.

     `.state('manager.staffDetail.view.schedule', {url:'', ...`
    
  • Or if you want to keep the URL of the default child route unchanged, then set up a redirect in your module.config. This code here will redirect any location of '/staff/{id}/view' to the location of '/staff/{id}/view/schedule':

     `$urlRouterProvider.when('/staff/{id}/view', '/staff/{id}/view/schedule');`
    
Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
Tim Kindberg
  • 3,605
  • 1
  • 25
  • 26
  • 4
    For the `$urlRouterProvider` redirect, I got an error when using a link with `ui-sref="manager.staffDetail.view"`, so I needed to remove `abstract: true` from the parent state declaration. – colllin Sep 08 '14 at 03:51
  • 4
    second option will not work for internal ui-sref links (it will not go to substate) – Stepan Suvorov Dec 15 '14 at 10:51
20

For setting default child view , check this example . On clicking Route 1 load default state route1.list

// For any unmatched url, send to /route1
$stateProvider
  .state('route1', {
      url: "/route1",
      abstract:true ,
      templateUrl: "route1.html"
  })
  .state('route1.list', {
      url: '',
      templateUrl: "route1.list.html",
      controller: function($scope){
        $scope.items = ["A", "List", "Of", "Items"];
      }
  })
  .state('route1.desc', {
      url: "/desc",
      templateUrl: "route1.desc.html",
      controller: function($scope){
        $scope.items = [];
      }
  })
  .state('route2', {
    url: "/route2",
    templateUrl: "route2.html"
  })
  .state('route2.list', {
    url: "/list",
    templateUrl: "route2.list.html",
    controller: function($scope){
      $scope.things = ["A", "Set", "Of", "Things"];
    }
  })
rab
  • 4,134
  • 1
  • 29
  • 42
  • If `route1.desc` has no url, you could still reach it with `$state.go`, but `route1.list` would be loaded by default on `/route1` :) this is useful if you don't want to give direct access to the state via url – Andrei Cojea May 01 '15 at 18:26
  • 2
    there is a big problem with this if we want to use the parent state in ui-sref – Kishore Relangi Jul 15 '15 at 10:23
  • you have to use href="/abstractroute" to link to the parent – Eric Shell May 14 '16 at 02:21
  • Can anyone explain to me why the `route1` state in this example needs a templateUrl? Or more generally, why would an `abstract:true` state need a template? – grant Jan 16 '17 at 18:47
  • the Parent of the child should have the one with `abstract:true` – aRyhan Oct 23 '18 at 09:56
9

I ran into the same issue and found this solution to work:

https://github.com/angular-ui/ui-router/issues/948#issuecomment-75342784

This is quoted from @christopherthielen on github

"For now, don't declare your state abstract, and use this recipe:"

 app.run($rootScope, $state) {
    $rootScope.$on('$stateChangeStart', function(evt, to, params) {
      if (to.redirectTo) {
        evt.preventDefault();
        $state.go(to.redirectTo, params)
      }
    });
  }

  $stateProvider.state('parent' , {
      url: "/parent",
      templateUrl: "parent.html",
      redirectTo: 'parent.child'
  });

  $stateProvider.state('parent.child' , {
      url: "/child",
      templateUrl: "child.html"
  });

Here is breakdown of how the process works:

  1. User navigates to state “parent”
  2. “$stateChangeStart” event gets fired
  3. Listener for “$stateChangeStart” catches event and passes “toState” (which is “parent”) and “event" to the handler function
  4. Handler function checks if “redirectTo” is set on “toState”.
  5. If “redirectTo” IS NOT set, nothing happening and the user continues on to the “toState” state.
  6. If “redirectTo" IS set, the event is canceled (event.preventDefault) and $state.go(toState.redirectTo) sends them to the state specified in “redirectTo” (which is “parent.child”).
  7. The “$stateChangeStart” event gets fired again, but this time “toState” == “parent.child” and the “redirectTo” option is not set, so it continues to “toState”.
Chuck D
  • 1,718
  • 4
  • 19
  • 26
7

Quite late but I think you can "redirect" to the state you want.

.state('manager.staffDetail.view', {
    url:'/view',
    templateUrl: 'views/staff.details.html',
    controller: 'YourController'
})

app.controller('YourController', ['$state',
function($state) {
    $state.go('manager.staffDetail.view.schedule');
}]);

You can write you controller right in the state config for short.

nvcnvn
  • 4,991
  • 8
  • 49
  • 77
  • 3
    I tried that, but it will double load controllers, resolves and anything that is onStateChange. I eneded up just using $urlRouterProvider.when('/app/service', '/app/service/list') – Meeker Mar 21 '14 at 15:48
  • will this load only twice or infinite times as the child state also invokes the parent state again as there we are redirecting again to child. – Kishore Relangi Jul 15 '15 at 10:25
  • If your navigation has the parent sref, it will not work on subsequent clicks - only on the initial controller load. – Jonathon Hill Nov 01 '16 at 20:12
3

I changed 'manager.staffDetial.view' to an abstract state and left the url of my default child state to blank ''

// Staff
.state('manager.staffList',    {url:'^/staff?alpha',      templateUrl: 'views/staff.list.html', data:{activeMenu: 'staff'}, controller: 'staffListCtrl'})
.state('manager.staffDetail',   {url:'^/staff/{id}', templateUrl: 'views/staff.html', data:{activeMenu: 'staff'}, controller: 'staffDetailsCtrl'})
.state('manager.staffDetail.view',   {url:'/view', abstract: true, templateUrl: 'views/staff.details.html', data:{activeMenu: 'staff'}})
    .state('manager.staffDetail.view.schedule', {url:'', templateUrl:'views/staff.view.schedule.html', data:{activeMenu: 'staff'}})
    .state('manager.staffDetail.view.history', {url:'/history', templateUrl:'views/staff.view.history.html', data:{activeMenu: 'staff'}})
    .state('manager.staffDetail.view.log', {url:'/log', templateUrl:'views/staff.view.log.html', data:{activeMenu: 'staff'}})
    .state('manager.staffDetail.view.files', {url:'/files', templateUrl:'views/staff.view.files.html', data:{activeMenu: 'staff'}})
.state('manager.staffDetail.edit',   {url:'/edit',  templateUrl: 'views/staff.edit.html', data:{activeMenu: 'staff'}})
Meeker
  • 5,979
  • 2
  • 20
  • 38
  • 13
    So you basically did exactly what I had suggested... why not give my answer the correct checkmark then? So I can get the rep points. – Tim Kindberg Jul 14 '13 at 04:56
  • I'm not sure you know what the word "exactly" means. However, your code example used the routeProvider method. I chose to not use that at all and instead keep the solution inside the state manager. This way the code stays clean and does not add any unnecessary extra lines of code. Your example was a possible answer, but not the most acceptable answer. – Meeker Jul 15 '13 at 14:59
  • It seemed as though everything you did, I had mentioned (though, yes, the code in my example was unneccessary). ME: "...you should set it to an abstract state." YOU: "I changed 'manager.staffDetial.view' to an abstract state..." ME: "Or a different option is give schedule an empty URL." YOU: "...and left the url of my default child state to blank". Seriously no hard feelings though, I'm glad you figured it out. – Tim Kindberg Jul 16 '13 at 12:58
  • 1
    Sorry, your answer was confusing, and not on point. After reading your explanation I see that you gave the separate options, and that in the end if I sort of combined your first and last option and left out the middle suggestion I would have a solution. I guess I see where you are going, but I'm not sure it would help the next reader if I marked yours as the answer. If you want to edit your answer and clear up your answer I'll be happy to give you the answer. – Meeker Jul 16 '13 at 16:23
  • Yeah you are right, I wrote the answer from my phone... :P I have cleaned up the answer, I hope it makes more sense now. Also I notice that if I search ui-router on Google this thread is the fourth result, so it **is** important that we have it be very clear. – Tim Kindberg Jul 19 '13 at 02:51
  • I made you the answer, how about plus one my answer then. – Meeker Jul 19 '13 at 15:53
1

In "angular-ui-router": "0.2.13", I don't think @nfiniteloop's redirect solution will work. It worked once I had rolled back to to 0.2.12 (and may have had to put the $urlRouterProvider.when call before the $stateProvider?)

see https://stackoverflow.com/a/26285671/1098564

and https://stackoverflow.com/a/27131114/1098564 for a workaround (if you don't want to go back to 0.2.12)

as of Nov 28 2014, https://github.com/angular-ui/ui-router/issues/1584 indicates that it should work again in 0.2.14

Community
  • 1
  • 1
sdoxsee
  • 4,451
  • 1
  • 25
  • 60
1

This is late but all the answers here don't apply to the latest version of angular-ui-router for AngularJS. As of that version (specifically @uirouter/angularjs#v1.0.x you can just put redirectTo: 'childStateName' in the second param of $stateProvider.state(). For example:

$stateProvider
.state('parent', {
  resolve: {...},
  redirectTo: 'parent.defaultChild'
})
.state('parent.defaultChild', {...})

Here is the relevant doc section: https://ui-router.github.io/guide/ng1/migrate-to-1_0#state-hook-redirectto

Hopefully this helps someone!

AndyPerlitch
  • 4,539
  • 5
  • 28
  • 43
0

here is a very simple and transparent alternative by just modifying parent state in ui router:

  .state('parent_state', {
    url: '/something/:param1/:param2',
    templateUrl: 'partials/something/parent_view.html',  // <- important
    controller: function($state, $stateParams){
      var params = angular.copy($state.params);
      if (params['param3'] === undefined) {
        params['param3'] = 'default_param3';
      }
      $state.go('parent_state.child', params) 
    }
  })

  .state('parent_state.child', {
    url: '/something/:param1/:param2/:param3',
    templateUrl: '....',  
    controller: '....'
  })
okigan
  • 1,559
  • 2
  • 18
  • 33
0

Add angular-ui-router-default and add the abstract and default options to the parent state:

...

.state('manager.staffDetail.view', {abstract: true, default: '.schedule', url:'/view',  templateUrl: 'views/staff.details.html', data:{activeMenu: 'staff'}})
  .state('manager.staffDetail.view.schedule', {url:'/schedule', templateUrl:'views/staff.view.schedule.html', data:{activeMenu: 'staff'}})

...

Note: for this to work, the parent template must have <ui-view/> somewhere in it.

Jonathon Hill
  • 3,445
  • 1
  • 33
  • 31