123

With ui-router, it's possible to inject either $state or $stateParams into a controller to get access to parameters in the URL. However, accessing parameters through $stateParams only exposes parameters belonging to the state managed by the controller that accesses it, and its parent states, while $state.params has all parameters, including those in any child states.

Given the following code, if we directly load the URL http://path/1/paramA/paramB, this is how it goes when the controllers load:

$stateProvider.state('a', {
     url: 'path/:id/:anotherParam/',
     controller: 'ACtrl',
  });

$stateProvider.state('a.b', {
     url: '/:yetAnotherParam',
     controller: 'ABCtrl',
  });

module.controller('ACtrl', function($stateParams, $state) {
   $state.params; // has id, anotherParam, and yetAnotherParam
   $stateParams;  // has id and anotherParam
}

module.controller('ABCtrl', function($stateParams, $state) {
   $state.params; // has id, anotherParam, and yetAnotherParam
   $stateParams;  // has id, anotherParam, and yetAnotherParam
}

The question is, why the difference? And are there best practices guidelines around when and why you should use, or avoid using either of them?

Merott
  • 7,189
  • 6
  • 40
  • 52

7 Answers7

67

The documentation reiterates your findings here: https://github.com/angular-ui/ui-router/wiki/URL-Routing#stateparams-service

If my memory serves, $stateParams was introduced later than the original $state.params, and seems to be a simple helper injector to avoid continuously writing $state.params.

I doubt there are any best practice guidelines, but context wins out for me. If you simply want access to the params received into the url, then use $stateParams. If you want to know something more complex about the state itself, use $state.

Matt Way
  • 32,319
  • 10
  • 79
  • 85
  • 1
    I found myself using `$state.params` in `ACtrl`, because I wanted to check if `yetAnotherParam` is set. So that if it isn't, I can do something. I'm not going into the details of that _something_ as it could warrant a question of its own. However, I feel I may be doing a _hack_ by checking for a parameter that is introduced by a child state and not recognised by the current state through `$stateParams`. I've found an alternative approach since. – Merott Apr 16 '14 at 22:38
  • 4
    Actually, the difference between the two is more than just a matter of context. $stateParams captures url-based params that $state considers applies to that state, *even if its child state contains more params*. $state.params seems to capture all url + non-url based params of the **current state** you are in. If you are in state `parent.child`, then `$stateParams` in `parentController` will evaluate url-based params of `parent`, but not those of `parent.child`. See [this issue](https://github.com/angular-ui/ui-router/issues/136). – Amy.js Mar 30 '15 at 21:18
  • 1
    On the other hand, $stateParams can preserve custom objects, types, etc. while $state.params would "convert custom objects into plain objects". – Amy.js Mar 30 '15 at 21:30
  • 2
    `$stateParams` works in resolve, while `$state.params` is incorrect (not showing params for state that is not resolved yet) – karaxuna Apr 14 '15 at 15:19
  • 1
    I found that the scope can $watch $state.params, but not $stateParams. I have no idea why. – Dennis Hackethal Sep 11 '15 at 23:24
  • I am guessing that is because `$state.params` is not a top level object, whereas `$stateParams` is. – Matt Way Sep 12 '15 at 00:23
  • 1
    $stateParams was deprecated in 1.0.0 https://gist.github.com/christopherthielen/c275b656f5b8bee81d327a68c809aa7b – Danie A Oct 28 '16 at 09:09
  • ```module.controller('ABCtrl', function($stateParams, $state) { $state.params; // has id, anotherParam, and yetAnotherParam $stateParams; // has id, anotherParam, and yetAnotherParam }``` @MattWay According to the documentation you linked, the stateParams of ABCtrl should have only yetAnotherParam. – Rushil Paul May 30 '17 at 06:50
19

Another reason to use $state.params is for non-URL based state, which (to my mind) is woefully underdocumented and very powerful.

I just discovered this while googling about how to pass state without having to expose it in the URL and answered a question elsewhere on SO.

Basically, it allows this sort of syntax:

<a ui-sref="toState(thingy)" class="list-group-item" ng-repeat="thingy in thingies">{{ thingy.referer }}</a>
Community
  • 1
  • 1
bbrown
  • 6,370
  • 5
  • 37
  • 43
14

EDIT: This answer is correct for version 0.2.10. As @Alexander Vasilyev pointed out it doesn't work in version 0.2.14.

Another reason to use $state.params is when you need to extract query parameters like this:

$stateProvider.state('a', {
  url: 'path/:id/:anotherParam/?yetAnotherParam',
  controller: 'ACtrl',
});

module.controller('ACtrl', function($stateParams, $state) {
  $state.params; // has id, anotherParam, and yetAnotherParam
  $stateParams;  // has id and anotherParam
}
Devid Farinelli
  • 7,514
  • 9
  • 42
  • 73
daerin
  • 1,347
  • 1
  • 10
  • 17
4

There are many differences between these two. But while working practically I have found that using $state.params better. When you use more and more parameters this might be confusing to maintain in $stateParams. where if we use multiple params which are not URL param $state is very useful

 .state('shopping-request', {
      url: '/shopping-request/{cartId}',
      data: {requireLogin: true},
      params : {role: null},
      views: {
        '': {templateUrl: 'views/templates/main.tpl.html', controller: "ShoppingRequestCtrl"},
        'body@shopping-request': {templateUrl: 'views/shops/shopping-request.html'},
        'footer@shopping-request': {templateUrl: 'views/templates/footer.tpl.html'},
        'header@shopping-request': {templateUrl: 'views/templates/header.tpl.html'}
      }
    })
Gavishiddappa Gadagi
  • 1,120
  • 1
  • 16
  • 34
Abdullah Al Noman
  • 2,817
  • 4
  • 20
  • 35
3

I have a root state which resolves sth. Passing $state as a resolve parameter won't guarantee the availability for $state.params. But using $stateParams will.

var rootState = {
    name: 'root',
    url: '/:stubCompanyId',
    abstract: true,
    ...
};

// case 1:
rootState.resolve = {
    authInit: ['AuthenticationService', '$state', function (AuthenticationService, $state) {
        console.log('rootState.resolve', $state.params);
        return AuthenticationService.init($state.params);
    }]
};
// output:
// rootState.resolve Object {}

// case 2:
rootState.resolve = {
    authInit: ['AuthenticationService', '$stateParams', function (AuthenticationService, $stateParams) {
        console.log('rootState.resolve', $stateParams);
        return AuthenticationService.init($stateParams);
    }]
};
// output:
// rootState.resolve Object {stubCompanyId:...}

Using "angular": "~1.4.0", "angular-ui-router": "~0.2.15"

addlistener
  • 871
  • 1
  • 12
  • 20
2

An interesting observation I made while passing previous state params from one route to another is that $stateParams gets hoisted and overwrites the previous route's state params that were passed with the current state params, but using $state.params doesn't.

When using $stateParams:

var stateParams        = {};
stateParams.nextParams = $stateParams; //{item_id:123}
stateParams.next       = $state.current.name;

$state.go('app.login', stateParams);
//$stateParams.nextParams on app.login is now:
//{next:'app.details', nextParams:{next:'app.details'}}

When using $state.params:

var stateParams        = {};
stateParams.nextParams = $state.params; //{item_id:123}
stateParams.next       = $state.current.name;

$state.go('app.login', stateParams);
//$stateParams.nextParams on app.login is now:
//{next:'app.details', nextParams:{item_id:123}}
Devid Farinelli
  • 7,514
  • 9
  • 42
  • 73
Shaun E. Tobias
  • 527
  • 3
  • 13
2

Here in this article is clearly explained: The $state service provides a number of useful methods for manipulating the state as well as pertinent data on the current state. The current state parameters are accessible on the $state service at the params key. The $stateParams service returns this very same object. Hence, the $stateParams service is strictly a convenience service to quickly access the params object on the $state service.

As such, no controller should ever inject both the $state service and its convenience service, $stateParams. If the $state is being injected just to access the current parameters, the controller should be rewritten to inject $stateParams instead.

Devid Farinelli
  • 7,514
  • 9
  • 42
  • 73
pabloRN
  • 866
  • 10
  • 19