Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to remove previously provided optional param when changing state with ui-router

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.

like image 382
DavidA Avatar asked Sep 10 '15 17:09

DavidA


2 Answers

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;
    });
}])
like image 146
GPicazo Avatar answered Nov 19 '22 10:11

GPicazo


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});
like image 33
Didier68 Avatar answered Nov 19 '22 10:11

Didier68