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.

sma
  • 9,449
  • 8
  • 51
  • 80

3 Answers3

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>
scarlz
  • 2,512
  • 1
  • 17
  • 20
  • 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. – Sunil D. Jul 26 '14 at 17:34
  • 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. – scarlz Jul 26 '14 at 17:48
  • 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 – sma Jul 28 '14 at 14:05
  • I like this solution. Thank you. Appreciate the idea with the directive. – sma Jul 29 '14 at 20:43
  • 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: ''` (I'm using ui-router 0.2.10, [here's](https://github.com/angular-ui/ui-router/wiki/Nested-States-and-Nested-Views) some examples of abstract states) – Jacob Ensor Jul 06 '15 at 17:29
  • Great answer! Thanks! – Artyom Pranovich Sep 02 '15 at 12:56
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

Community
  • 1
  • 1
sma
  • 9,449
  • 8
  • 51
  • 80
-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.

Sunil D.
  • 17,983
  • 6
  • 53
  • 65
  • I like this solution, thank you for the help. (I accepted the other answer because of the directive idea). – sma Jul 29 '14 at 20:44