9

Is there anyway to specify a default parameter for every route using the Angular UI Router?

My app is entered through the context of another application by selecting a user and then navigating to my application. The URL in my application will always have the user ID in the URL so that people can bookmark the URL, email it, etc. So, as you navigate around, the URL always follows a scheme of:

#/{userId}/view/...
#/{userId}/edit/...

etc.

This userId will always be the same for a user inside the app for any route they go to. If they happen to log out, go back to the main app, select a new user and come back to my app, this userId will change, but will be the same value for every route.

Is there anyway to read this value from say a service/factory and then plug it into every route?

EDIT:

I should mention I want to avoid having to explicitly set this parameter on every route when I navigate to a state. For example, I don't want to have to do ui-sref="new-state({userId : blah})" every time I navigate to a new state. That userId will never change in the context of my application.

EDIT AGAIN:

I actually went about this a different way concerning the requirement to not have to send 'userId' to every route manually. Instead of using a directive, I used a $provide.decorator to add this functionality to the 'go' method. I've added an answer below to what I did.

3 Answers 3

7

You can declare an abstract parent state from which child states inherit:

$stateProvider
  .state('user', {
     url: '/:userid',
     abstract: true,
     resolve: 
       // assuming some kind of User resource factory
       currentUser: function($stateParams, User) {
         return User.get($stateParams.userid);
       }
     }
   })
  .state('user.view', {
     url: '/view', // actual url /:userid/view
     controller: function($scope, currentUser) {
       // currentUser resource available
     }
   });
  .state('user.edit', {
     url: '/edit', // actual url /:userid/edit
     controller: function($scope, currentUser) {
       // currentUser resource available
     }
   });

In terms of navigating to a state, you need to pass in the desired user:

$state.go('user.view', {userid: 'myuserid'});

As a consequence it might make sense to create some kind of .go() wrapper method on your currentUser service, so that you needn't specify the user id each time.

UPDATE:

To counter the problem posted in your edit, you could introduce a directive like this:

angular.module('app')
  .directive('userSref', function($state) {               
     return function(scope, elem, attrs) {
       var state = 'user.' + attrs.userSref;

       elem.bind('click', function() {
         $state.go(state, {userid: $state.params.userid});
       });           

       scope.$on('$destroy', function() {
         elem.unbind('click');
       });
     };
  });

Then, any future links to user-based states can be done so with:

<a user-sref="view">View User</a>
Sign up to request clarification or add additional context in comments.

5 Comments

LOL, that's basically what I said w/out the abstract state :) This is desirable when the app doesn't need the top level state except for providing a "base" state to inherit view templates, controller functionality, and parameters from.
By needing the top level state, do you mean a transition-able state at the /:userid url? This is still possible with an abstract state. I find abstract state is ideal in use cases such as the one presented in the question, as it serves as a base state, like you say.
One thing I'm trying to avoid, though is having to pass the userId to every route. I want it to be plugged in automatically from a service or something. I added that comment to my original question
I like this solution. Thank you. Appreciate the idea with the directive.
Wanted to point out that I needed to add a template attribute to the parent abstract state, as it needs a ui-view for the children to render in. template: '<ui-view/>' (I'm using ui-router 0.2.10, here's some examples of abstract states)
3

Instead of writing a directive that handled the auto-sending of userID, I used $provide.decorator as follows:

app.config(['$provide',
    function($provide) {
        $provide.decorator('$state', function($delegate, UserService) {

            // Save off delegate to use 'state' locally
            var state = $delegate;

            // Save off reference to original state.go
            state.baseGo = state.go;

            // Decorate the original 'go' to always plug in the userID
            var go = function(to, params, options) {

                params.userID = UserService.userID;

                // Invoke the original go
                this.baseGo(to, params, options);
            };

            // assign new 'go', decorating the old 'go'
            state.go = go;

            return $delegate;
        });
    }
]);

I got the idea from this post:

Changing the default behavior of $state.go() in ui.router to reload by default

Comments

-1

You can use the "nested states" and "resolves" features of UI-Router to create a hierarchy of states in your app. You'll define a top level state that resolves the userId. Then define any number of child states, and they will automatically inherit the "resolved" userId.

Check out this page of the documentation, in particular the section titled "Important $stateParams Gotcha". I will paste the two code snippets from that page here.

Incorrect method:

$stateProvider.state('contacts.detail', {
   url: '/contacts/:contactId',   
   controller: function($stateParams){
      $stateParams.contactId  //*** Exists! ***//
   }
}).state('contacts.detail.subitem', {
   url: '/item/:itemId', 
   controller: function($stateParams){
      $stateParams.contactId //*** Watch Out! DOESN'T EXIST!! ***//
      $stateParams.itemId //*** Exists! ***//  
   }
})

Correct method using "resolves":

$stateProvider.state('contacts.detail', {
   url: '/contacts/:contactId',   
   controller: function($stateParams){
      $stateParams.contactId  //*** Exists! ***//
   },
   resolve:{
      contactId: ['$stateParams', function($stateParams){
          return $stateParams.contactId;
      }]
   }
}).state('contacts.detail.subitem', {
   url: '/item/:itemId', 
   controller: function($stateParams, contactId){
      contactId //*** Exists! ***//
      $stateParams.itemId //*** Exists! ***//  
   }
})

Since the "contactId" parameter is resolved by the parent state, the child state will inherit that.

1 Comment

I like this solution, thank you for the help. (I accepted the other answer because of the directive idea).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.