Skip to content

Using optional parameters reloads controller when moving away from state #1673

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
eranimo opened this issue Jan 5, 2015 · 23 comments
Closed
Assignees
Labels
Milestone

Comments

@eranimo
Copy link

eranimo commented Jan 5, 2015

I define a state like this:

.state('game.sector', {
    url: '/universe/sector/:sector_id_id/:x/:y',
    params: {
        system_id: null,
        x: {
            value: 1,
            squash: true
        },
        y: {
            value: 1,
            squash: true
        }
    },
    controller: 'SectorCtrl',
    templateUrl: 'modules/sector/views/sector.html'

})

With two optional parameters and one required.
I have a function that periodically updates the X and Y optional parameters like so:

$state.transitionTo('game.sector', {
    sector_id: $scope.sector_id,
    x: x
    y: y
}, {
    location: 'replace',
    relative: $state.$current,
    notify: false
});

The problem is that after I update the optional parameters, and after I trigger any kind of state change (ui-sref or $state.go) the state transition sometimes fails.
I'm also listening on every UI Router even on the $rootScope, but this failure is never reported to those events. Clicking on a ui-sref link simply reloads the current controller.
Any ideas why this may happen?

@jessezhang91
Copy link

I believe I'm experiencing the same (or a related) issue. I'm using Angular 1.3.8. I don't think this occurs in 1.2.*.

In my case, if I navigate to the same state with different params and notify false then navigate to another state, the first state's controller is initialized again between the start and success of the state change.

Here's a plunker replicating this problem: http://plnkr.co/eOtj7C6mMgK8qwVqBEXi

Console log:

main.js:1 Scripts loading... 
main.js:23 Start:   {} -> home{}
main.js:24 Success: {} -> home{}
main.js:9 home state controller init
main.js:23 Start:   home{"state":"1"} -> foo{"state":"2"}
main.js:9 home state controller init
main.js:24 Success: home{"state":"1"} -> foo{"state":"2"}
main.js:10 foo state controller init
main.js:23 Start:   foo{"state":"2"} -> home{"state":"3"}
main.js:24 Success: foo{"state":"2"} -> home{"state":"3"}
main.js:9 home state controller init

Problematic part:

main.js:23 Start:   home{"state":"1"} -> foo{"state":"2"}
main.js:9 home state controller init
main.js:24 Success: home{"state":"1"} -> foo{"state":"2"}

The home state controller is initialized while the state is being changed.

@jessezhang91
Copy link

I believe line 1047 of state.js is causing this to occur.

resolved = resolveState(state, toParams, state === to, resolved, locals, options);

@eranimo
Copy link
Author

eranimo commented Jan 27, 2015

I currently have a workaround that involves writing my own ui-sref replacement that retries state transitions until they pass. I send an event in that case and listen to it on controller init to make sure the controller can safely reload.

A resolution to this issue would be critical. This effectively breaks optional parameters and requires a lot of hacks.

EDIT: I am running Angular 1.3.0

@chrisguerrero
Copy link

+1

I'm currently experiencing the same issue as @jessezhang91. When I update my search params by calling $state.go to the current state with options notify: false, location: 'replace' and inherit: false and then go to a different state, the controller from the original state is loaded again.

I'm using angular 1.2.28 and ui-router 0.2.13

@sebholstein
Copy link

@chrisguerrero I have the same problem. Even if inherit is true. Do you have a workarround?

@jessezhang91
Copy link

I have not found a workaround. Rather, I just let the controller run again but make sure it doesn't do anything to change states.

I initially thought setting reloadOnSearch to false and setting notify to true would work but that doesn't seem to change the result for Angular 1.3.

@chrisguerrero
Copy link

@SebastianM My issue stemmed from the fact that I was using UI router in a non single page app. I was trying to pass parameter values across page requests, which is why I needed to reload the state. I've since moved to a true SPA and can now pass those parameters on state change using $state.go

@kevinob11
Copy link

I'm seeing this same issue in an SPA. I update params by using $state.go with notify: false and then when I navigate away the controller reloads. My workaround has been to make sure that the controller reloading doesn't cause an additional state change, but it's a very wonky workaround.

@kevinob11
Copy link

Any movement on this?

@eranimo
Copy link
Author

eranimo commented Apr 15, 2015

Probably not. I've completely abandoned Angular and this framework was a large part of that decision

@RickWalker
Copy link

This is exactly the problem I have - I'm updating the url with optional parameters in response to user selections with notify:false state changes, and then when I try to navigate to a different state, it re-initialises the current state too.

@jessezhang91
Copy link

It seems this is still an issue with ui-router 0.2.14 and angular 1.3.13.

I noticed that the hash does not change when this issue occurs. I wrote a workaround that decorates the $state.transitionTo function to detect when it does a "notify: true" transition after having done a "notify: false" transition. After that transition has successfully occurred, I save the "fromState" from the "$stateChangeSuccess" event and the hash from window hashchange. The next call to $state.transitionTo that has the same hash and same state within 500ms will be ignored. Craziest workaround but it seems to work for my case.

This is definitely not a fix but, looking through the source code, there's no obvious way to make it work properly.

$provide.decorator("$state", ["$delegate", "$rootScope", "$window",
    function ($delegate, $rootScope, $window) {
        var $state = $delegate;
        var transitionTo = $state.transitionTo;

        var notifyIssue, notifyTransition, notifyState, notifyHash, notifyTimeout;
        resetNotifyIssue();

        $rootScope.$on("$stateChangeSuccess", function (event, toState, toParams, fromState) {
            if (notifyIssue && notifyTransition) {
                notifyState = fromState.name;
                // unfortunately, it's a non-deterministic amount of time, must timeout for a bit
                notifyTimeout = setTimeout(resetNotifyIssue, 500);
            }
        });

        $($window).on("hashchange", function () {
            if (notifyIssue && notifyTransition) {
                notifyHash = $window.location.hash;
            }
        });

        $state.transitionTo = function (to, toParams, options) {
            if (notifyTransition && (to.name || to) === notifyState && $window.location.hash === notifyHash) {
                resetNotifyIssue();
                return undefined;
            }
            if (options && options.notify === false) {
                notifyIssue = true;
            } else if (notifyIssue) {
                notifyTransition = true;
            }
            return transitionTo.apply($state, arguments);
        };
        return $delegate;

        function resetNotifyIssue() {
            clearTimeout(notifyTimeout);
            notifyTransition = false;
            notifyIssue = false;
            notifyState = null;
            notifyHash = null;
        }
    }
]);

@zaggino
Copy link

zaggino commented May 26, 2015

This is quite bad, how come it has not been properly fixed in 5 months?

@jessezhang91
Copy link

I dug into the code a bit and found that the issue occurs because $viewContentLoading is being emitted in line 63 of view.js before $stateChangeSuccess.

The first transition with notify: false does not trigger $viewContentLoading or $stateChangeSuccess. The second transition with notify: true does trigger it but because $viewContentLoading is emitted before the new global $state has been set, the previousLocals === latestLocals in line 215 of viewDirective.js will always be false, which will allow the transclude in line 219 to occur and the inner transclusion will initialize the controller.

Afterwards, $stateChangeSuccess is emitted and this causes the transclusion to occur again with the correct set of templates and controllers.

Luckily, it seems that PR #2003 fixes this interaction.

It seems to fix the issue in my project and it passes my plunker: http://plnkr.co/edit/UoC6rk2zKQItQNji0OPr

Start:   {} -> home{}
Success: {} -> home{}
home state controller init
Start:   home{"state":"1"} -> foo{"state":"2"}
Success: home{"state":"1"} -> foo{"state":"2"}
foo state controller init
Start:   foo{"state":"2"} -> home{"state":"3"}
Success: foo{"state":"2"} -> home{"state":"3"}
home state controller init

@btm1
Copy link

btm1 commented Jul 30, 2015

+10

@sebastiannm
Copy link

+1

@christopherthielen
Copy link
Contributor

@btm1 you only get one vote.

@christopherthielen christopherthielen added this to the 0.2.16 milestone Aug 3, 2015
@PawelGlow
Copy link

Hi guys, any luck with this one? +1

@andertun
Copy link

+1
Any news about this bug?

@kevinob11
Copy link

This should be fixed by #2003 which was just merged this morning.

@christopherthielen
Copy link
Contributor

Fixed in f9b43d6

@pelepelin
Copy link

@christopherthielen
I tried f9b43d6 and the issue mentioned in the second comment #1673 (comment) is not fixed for me. Is it supposed to be fixed or is it another issue?

@christopherthielen
Copy link
Contributor

@checat that is likely a side effect of using notify: false. The $stateChange* events are deprecated and removed by default in 1.0, and thus so is notify: false deprecated. It's always been up the caller to manage the side effects of muting the state change events, and it seems that it's nearly impossible to get it right.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests