29

I'm having one parent state that has two children's state inside that I'm going to show one state based on the URL.

Out of those two states one is having to parameters like param1 and param2, I have use params option of ui-router inside state definition.

State

$stateProvider.state('tabs.account', {
    url: '/account',
    views: {
        'content@tabs': {
            templateUrl: 'account.html',
            controller: function($scope, $stateParams) {
                //This params are internally used to make ajax and show some data.
                $scope.param1 = $stateParams.param1;
                $scope.param2 = $stateParams.param2;
            },
        }
    },
    params: {
        param1: { value: null }, //this are optional param
        param2: { value: null } //because they are not used in url
    }
});

If you look at my route the params option is not really introduced inside the URL, that's why I'm considering then as optional.

Problem

Look at plunkr, I've shown two tabs Account & Survey,

  1. Click on Survey tab, then add some data in the textarea which are shown.
  2. Click on Go to Account that will pass those textarea values to the other Account tab by doing ui-sref="tabs.account({param1: thing1, param2: thing2})" on the anchor

  3. Now you will see the param1 & param2 values on html which has been assigned to scope from $stateParams

  4. Now again Click on Survey tab, you will land on the survey page.
  5. Just click browser back, you will notice that param value is not getting null.

Problem Plunkr

I believe you got what I wanted to ask, why the optional parameter value has not been store? as they have been a part of state.

I know I can solve this issue by below two solutions.

  • By creating one service that will share data between two views.
  • By adding parameter inside the state URL. like url: '/account/:param1/:param2', (But i wouldn't prefer this)

I already tried angular-ui-routers sticky states but that doesn't seems to work for me. What is the better way to this?

Is there any way by which I can make my use case working, Any ideas would appreciate.

Github Issue Link Here

Pankaj Parkar
  • 134,766
  • 23
  • 234
  • 299
  • 1
    Just found this https://github.com/angular-ui/ui-router/issues/1158 about the "notify" attribute that could be set to false to prevent events (such as reloading controller) but it is actually not working (bug). – Okazari Jul 23 '15 at 15:15
  • @Okazari thanks for comment..I will need to put watch on this issue too :( – Pankaj Parkar Jul 23 '15 at 15:20
  • Did you consider using the query params instead of url params ? It may works and will be a little less weird in the url ('/url/route?param1=value&param2=value') – Okazari Jul 24 '15 at 07:12
  • @Okazari Thanks for suggestion, but I don't wanted to do in that way..Its basically the same thing which I'm doing here – Pankaj Parkar Jul 24 '15 at 07:18
  • @downvoter please do comment while you do downvote..Is there any problem with a question? – Pankaj Parkar Sep 04 '15 at 12:26

4 Answers4

21

I would move the params definition to the parent state, so as to share the optional state params between your two child states.

The child states will inherit the $stateParams from your parent, as such there is no real 'workaround' needed.

Simply inject $stateParams as per usual in your child controllers and you will have full access to the params being passed around. If you don't want to utilise the params in a specific child state, simply avoid injecting them.

This works with;

  • Back button
  • Forward button
  • ui-sref (without params (will keep as-is))
  • ui-sref (with params (will overwrite))

$stateProvider
  .state('parent', {
    params: { p1: null, p2: null }
  })
  .state('parent.childOne', {
    url: '/one',
    controller: function ($stateParams) {
      console.log($stateParams); // { p1: null, p2: null }
    }
  })
  .state('parent.childTwo', {
    url: '/two',
    controller: function ($stateParams) {
      console.log($stateParams); // { p1: null, p2: null }
    }
  })

If you at any point want to clear the params while travelling within the state tree of parent, you would have to do so manually.

That would be the only real caveat I can see by using this solution.

I realise manual clearing may not be desirable in the case you present, but you haven't taken an active stand against it, as such I feel the suggestion has merit.

updated plunker

  • This is also a good solution. Just a note that it may require more param management if you want the params to persist when navigating from other states (imagine going somewhere totally different in the app or externally, and then using the browser back button - not sure if that is one of your use cases). – Christian Yang Jul 26 '15 at 04:41
  • Params dont persist if you move to a state has no notion of the params in place, same thing goes for the back button. If the state knows about the params coming in, all is fine. So you could keep shuffling the single params definition higher up in the state hierarchy - but when it starts getting silly, I would move it into a service. –  Jul 26 '15 at 09:17
  • The use case I envision for sharing **params,** is a few couple of states that have a logical tie to the params being passed around. No way would I ever recommend using it if the idea is to pass data around and persist it throughout a large part of / the entire application. –  Jul 26 '15 at 09:19
  • @KasperLewau any suggestions when should I clear those values..whats you thought on it? – Pankaj Parkar Jul 29 '15 at 20:23
  • @PankajParkar Well, since you are sharing the values between all child-states of the parent that contains the `params` attribute, it would clear out automatically once you transition out of that state tree and then **in** again, without passing the params upon entrance. It really depends on your use case, when do you **need** to clear it? –  Jul 29 '15 at 22:01
  • @KasperLewau I'd love this appoach which suits my use case – Pankaj Parkar Jul 30 '15 at 15:06
5

One workaround solution is to cache the state params and conditionally load them when entering the tabs.account state. UI Router state config actually lets you provide an onEnter callback for these types of "do something on entering the state" situations.

Here's the basic logic using localStorage as the cache, with working Plunker here:

  • When you enter the tabs.account state, check for your state params
    • If you have them, cache them to local storage
    • If you don't, load them from local storage into $stateParams

Here's an example code snippet for reference (taken from the Plunker):

  $stateProvider.state('tabs.account', {
    ...
    onEnter: ['$stateParams', '$window', function($stateParams, $window) {
      if($stateParams.param1) {
        $window.localStorage.setItem('tabs.account.param1', $stateParams.param1);
      } else {
        $stateParams.param1 = $window.localStorage.getItem('tabs.account.param1');
      }
      if($stateParams.param2) {
        $window.localStorage.setItem('tabs.account.param2', $stateParams.param2);
      } else {
        $stateParams.param2 = $window.localStorage.getItem('tabs.account.param2');
      }
    }],
    ...
  }

One caveat is that your params will persist indefinitely (e.g. across refreshes and sessions). To get around this, you could clear out the cache on application load like in app.run.

One last note is that in the Plunker, I'm accessing local storage directly (through the Angular $window service). You might want to use some AngularJS module - I've used angular-local-storage in production.

Christian Yang
  • 452
  • 3
  • 12
  • 1
    If you doesn't want to persist indefinitely just use the SessionStorage instead of the LocalStorage. The sessionStorage have a lifetime of an app session (Refresh/close clear the sessionstorage) – Okazari Jul 24 '15 at 15:40
  • @Okazari does the sessionStorage/localStorage will work in old browser like IE9/IE10? – Pankaj Parkar Jul 25 '15 at 06:13
  • @PankajParkar IE9/10 has full support for both those technologies. [caniuse?](http://caniuse.com/#feat=namevalue-storage) –  Jul 25 '15 at 08:20
  • @Okazari this looks promissing then..will come back to this answer..after my some testing – Pankaj Parkar Jul 25 '15 at 08:22
  • @PankajParkar Not trying to poach for acceptance, but have you taken a look at my suggested answer? Arguably the cleanest of the bunch. –  Jul 25 '15 at 08:40
  • 1
    @KasperLewau yes I'm looking at..currently I don't have mine code here..I'll definitely look at it on my monday morning..I already upvoted your answer dude.. :) – Pankaj Parkar Jul 25 '15 at 08:42
  • @PankajParkar Very well then! Do apologise my pestering. *moving along* oh and this is absolutely not to say that the local/sessionStorage solution is a bad one - not at all. –  Jul 25 '15 at 08:44
  • @KasperLewau Totally. As you said, despite the fact yours looks cleaner using local/session storage isn't that weird. We used it on my first angular project (this was a choice made by angular's experts) to save some navigation informations (Like scroll position in an infinite scroll, search filters etc...) – Okazari Jul 25 '15 at 20:02
  • @ChristianYang I checked your plunkr..how should I clear the values..everytime they are persisting.I think its similar as that of service/factory approach on its relaced by `localStorage`/`sessionStorage` – Pankaj Parkar Jul 29 '15 at 20:12
  • Hi @PankajParkar. Do you know under what circumstances you'd like to clear the values? Is it on page refresh, similar to a web form? If so, you can call `localStorage.clear()` (or `sessionStorage.clear()`) in `app.run`. Here's an updated plunkr with clearing in `app.run` and using `sessionStorage`: http://plnkr.co/edit/1LFTxN?p=preview. If you let me know other scenarios where you'd want the values cleared, would be happy to go through those too. – Christian Yang Jul 30 '15 at 00:43
  • And to be fair, you're right that this workaround is in some ways similar to using storage as a shared service equivalent. Probably the most useful piece in your case is using the state `onEnter` callback to retrieve from the shared service/storage/other cache so that by the time you get to your state controller, you can just use the `$stateParams`. At least that way the concern of persisting the optional params is abstracted away from the controller code. – Christian Yang Jul 30 '15 at 00:49
2

I believe that what you want to achieve is not possible without using one of the two solution you provided.

The browser back-button is just keeping the URL history. He have no clue about the ui-router internal states and will just force the URL to change.

Forcing the URL to change will trigger internal ui-router machine but unfortunately ui-router will see the URL change the same as if someone would have change the url by hand.

Ui-router will fire a new route change to the route pointed by the URL. That mean he doesn't know you wanted to go "back" and will just change state to the new one without any parameters.

Summary

Clicking on back button will fire a state change to a new state according to the URL instead of going back to the previous state.

This is why adding the params to the URL solve the issue. Since the URL is discriminatory you'll finally land on the state you wanted.

Hope it helped.

Okazari
  • 4,597
  • 1
  • 15
  • 27
0

To me this sounds as X Y problem. There are suggested ways to make state params persistent whereas the problem is located out of this surface.

By definition of the question there is data that should be kept independent of states. So it is kind of global relative to states. Thus there should be a service that keeps it and controllers of both states maintain it as required. Just don't pass data that is out of one state's scope in state params.

Sticky states would fit this approach easily since it allows to keep DOM and $scope while another state is active. But it has nothing to do with state params when the state is reactivated.

Community
  • 1
  • 1
Kirill Slatin
  • 6,085
  • 3
  • 18
  • 38
  • @krill Could you provide me an example with sticky states.. I tried it but that didn't work for me..Thanks :) – Pankaj Parkar Jul 28 '15 at 05:39
  • @PankajParkar Sure [thing](http://plnkr.co/edit/UT06RG8tNEgC225RqZjV?p=preview). For this case you even don't need sticky states from ui-router-extras. Let me know if you need help with a more complicated scenario (which I bet you have for the real purpose) – Kirill Slatin Jul 28 '15 at 06:10
  • @krill The plunkr you made, had followed one of the approach which is **service approach** which I suggested in my question itself – Pankaj Parkar Jul 28 '15 at 06:16
  • @PankajParkar this is truly the optimal approach for this task and I explained why. The sticky solution would look like [this](http://plnkr.co/edit/eoQzIA4f1REmXF7Qs3yZ?p=preview). Your problem was the fact that sticky states should occupy a [different named ui-view each](http://christopherthielen.github.io/ui-router-extras/#/sticky#api). However this approach doesn't transfer params into `account` when clicking in menu. – Kirill Slatin Jul 28 '15 at 06:37