20

I have a state with an optional query parameter. I thought it was working fine, but I discovered that once I specify the optional parameter, it is retained from that point on, as I navigate to the same state with other parameters, unless the optional parameter is explicitly removed.

Is there a way to make sure the optional parameter is remove between state changes?

This is an example of how I want to change state (test a and test b), but it turns out I have to use the 'test d' version because of the retention of the optional parameter if it has been used previously.

<a ui-sref="test({req: 'a'})">test a</a><br/>
<a ui-sref="test({req: 'b'})">test b</a><br/>
<a ui-sref="test({req: 'c', opt: 'here'})">test c and opt</a><br/>
<a ui-sref="test({req: 'd', opt: undefined})">test d and explicity without opt</a>

This is similar to how my state is configured:

        $stateProvider.state('test', {
            url: '/{req}?opt',
            params: {
                opt: {value: null, squash: true}
            },
            template: '<h1>req = {{req}}</h1>' +
                    '<h1>opt = {{opt}}</h1>',
            controller: function($scope, $stateParams) {
                $scope.req = $stateParams.req; 
                $scope.opt = $stateParams.opt;
            }
        })

Here is a jsfiddle. Click on the first two links, and it works as expected, but once you click on the 3rd link, and then click back on either of the first two links, you will see that the optional parameter is retained, even though it is omitted in the first two links.

I would prefer to not have to explicitly reset the optional parameter, and have it actually work as it if is optional.

Any ideas? Is this how it is designed to work? If so, is there a way to get it to work the way I want? Or have I discovered a bug?

Update

It appears that retaining the optional param is 'as designed' based on one of the answers below. However, I am still looking at an efficient workaround, if possible. I transition to the state from a lot of places in my app, and 99% of them don't specify the optional parameter. I would prefer not to have to modify all of those transitions and make them know about the optional parameter, and instead, only specify the optional parameter the 1% of the time I need it.

I've tried to create two states "test" and a child "test.opt" and then transition to "test" normally (ie, 'test a' and 'test b'), and only transition to "test.opt" when I specify the optional parameter. However, I have a resolve which a rest call and it needs to know if the opt parameter is set, so that doesn't work as the resolve can't access the 'opt' from the child.

Update #2

I've also tried making a duplicate of the 'test' state, named 'testopt' in which case the only difference is the ?opt parameter. However, apparently ui-router cannot distinguish between two urls that differ only by query param, and therefore it still tries to transition to 'test' even if the opt param is part of the url.

DavidA
  • 3,984
  • 5
  • 25
  • 38
  • Seems like you are asking about this http://stackoverflow.com/questions/31542972/how-to-persist-optional-parameter-on-browser-back-in-ui-router And there is bug logged against the same https://github.com/angular-ui/ui-router/issues/2115 – Pankaj Parkar Sep 10 '15 at 17:24
  • I don't think it is the same issue. That question was about wanting to retain the optional parameter so 'back' works as expected. My problem is that is being retained, even when navigating to the state newly. The discussed solution to the bug is to cache the state params so they are accessible when going 'back'. Also, as you can see, I am actually using the param on the url, which is a bit different. The solution to the SO question says I have to manually remove the parameter, which is what I am trying to find an alternative for. – DavidA Sep 10 '15 at 17:40

2 Answers2

25

Simply add the ui-sref-opts option of inherit=false to your links:

<a ui-sref="test({req: 'a'})" ui-sref-opts="{inherit: false}">test a</a><br/>
<a ui-sref="test({req: 'b'})" ui-sref-opts="{inherit: false}">test b</a><br/>
<a ui-sref="test({req: 'c', opt: 'here'})" ui-sref-opts="{inherit: false}">test c and opt</a><br/>
<a ui-sref="test({req: 'd', opt: undefined})" ui-sref-opts="{inherit: false}">test d and explicity without opt</a>

Thread that documents this feature request and implementation: https://github.com/angular-ui/ui-router/issues/376

---- EDIT ----

Since the above solution does not seem to be ideal because it also requires updating (and maintaining) each of the ui-sref anchors to set a desired behavior, another solution could be to override the state's go(...) method to set the inherit option to false by default. While I am not sure if this is a recommended pattern, to do it, change your .config to the following:

.config(['$stateProvider', '$provide', function ($stateProvider, $provide) {
    $stateProvider.state('test', {
        url: '/{req}?opt',
        params: {
            opt: {value: null, squash: true}
        },
        template: '<h1>req = {{req}}</h1>' +
            '<h1>opt = {{opt}}</h1>',
        controller: function($scope, $stateParams) {
            $scope.req = $stateParams.req; 
            $scope.opt = $stateParams.opt;
        }
    });

    $provide.decorator('$state', function ($delegate) {
        var state = $delegate;

        state.baseGo = state.go;

        var go = function (to, params, options) {
            options = options || {};

            if (angular.isUndefined(options.inherit)) {
                options.inherit = false;
            }

            this.baseGo(to, params, options);
        };

        state.go = go;
        return $delegate;
    });
}])
GPicazo
  • 6,516
  • 3
  • 21
  • 24
  • That is an option, but doesn't seem much different than setting opt to undefined, like in 'test d'. the situation is that 99% of my links are of the first type, and only one place do I use the optional value. I don't want to have to have all 99% of the other locations account for an 'optional' parameter, if possible. – DavidA Sep 10 '15 at 18:47
  • That is much closer to what I want, *almost* exactly. However, there are two problems. First is that it appears that options.inherit is set by default, so your check if it is undefined always fails. However, even if I force it to false here. The links built up via ui-sref still include the inherited parameter. The navigation works corectly, but if a user were to right click and 'save link', the param would be there. Thanks for the help and suggestions. – DavidA Sep 10 '15 at 20:38
  • In my tests, options.inherit is NOT set by default. Did you remember to remove the ui-sref-opts directive? The two solutions above are exclusive, so it's one or the other. – GPicazo Sep 10 '15 at 20:42
  • Strange. I never actually put in the ui-sref-opts, and even searched my whole code base to see if I was setting inherit anywhere, yet it was still defined as it came in. However, in answer to the second issue, I can solve it by also decorating the $state.href method similarly. – DavidA Sep 10 '15 at 20:44
  • It 'was' coming in defined, or 'is' coming in defined? Is the problem solved? – GPicazo Sep 10 '15 at 20:46
  • I see what is going on. My state IS actually a child of another state, but that state is completely unrelated to the parameters I am working with, so I assume that is what is causing inherit to be defined by default. I can work with that, and by decorating href, I can solve the other issue, so I will consider this to be answered successfully. Thanks a bunch! – DavidA Sep 10 '15 at 20:49
15

I had the same problem, because of default inheritance of parameters... To complete the previous answer, the syntaxe for JS call is:

$state.go('new state', {pararemeters... } , {inherit:false});
Didier68
  • 1,027
  • 12
  • 26