Skip to content
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

Two back button clicks needed to get back when the initial state has got a redirect #3187

Closed
NeoGenet1c opened this issue Dec 6, 2016 · 8 comments
Milestone

Comments

@NeoGenet1c
Copy link

NeoGenet1c commented Dec 6, 2016

UI router v1.0.0-beta.3 and angular v1.5.9

When an app loads its initial state for the first time and it contains a redirect, the transition graph looks something like following:

State A [redirected] -> State B [success]

However, if a user clicks on the back button, UI router tries to jump back to State A causing another redirect (meaning that nothing happens). In this case, transitions look like this:

State A [redirected] -> State B [ignored]

Finally, another click on the back button will send me back to wherever I was before the application/first state loaded (eg. Google's SERP).

I would expect that a first click on the back button will send a user to a previous page. This issue only occurs if the very first loaded state is a redirect - if I get to a state with a redirect through ui-sref link within my own app, the back button click will send me back right away.

@christopherthielen
Copy link
Contributor

Agreed, that's undesired behavior.

are you using pushstate or hashbang urls?

Would you be able to add {location: 'replace'} to your transition options when doing the initial redirect?

I think this should be solvable by the router. If the initial transition was caused by a url synchronization then when the final state is activated, the url should be replaced.

In the redirect code we could probably enable { location: 'replace' } if the old transition was initiated by a url sync (options.source === 'url') and no prior location: option was provided (by checking the original targetState())

@christopherthielen christopherthielen added this to the 1.0.0-final milestone Dec 15, 2016
@NeoGenet1c
Copy link
Author

I use pushstate URLs ($locationProvider.html5Mode({ enabled: true }).

Can I (somehow) define transition options inside StateDefinition's Object or do I have to use hooks and detect it is first run and first redirect?

@christopherthielen
Copy link
Contributor

Are you using redirectTo?

it would be something like this:

    redirectTo: trans => {
      let $state = trans.router.stateService;
      let firstTrans = trans.$id === 0;
      let options = { location: (firstTrans ? 'replace' : true) };
      return $state.target("home.foo", {}, options);
    }

http://plnkr.co/edit/3arZH4m0iuaUlVBw06So?p=preview

@NeoGenet1c
Copy link
Author

NeoGenet1c commented Dec 15, 2016

Yep, haven't realised that redirectTo can take a callback :) This is what indeed DOES work for me in Angular1:

parentState.redirectTo      = function( $transition ) {
  var $state      = $transition.router.stateService,

  firstTransition = angular.isUndefined( $transition.id ),
  options         = { location: ( firstTransition ? 'replace' : true ) };

  return $state.target( childrenState.name, $transition.params(), options);
}

Btw, Thanks for the great work on new router @christopherthielen!

@chaserstrong
Copy link

chaserstrong commented Dec 15, 2016

@NeoGenet1c @christopherthielen Have you solved this issue? I found it may be near these codes:

$rootScope.$watch(function $locationWatch() {
      var oldUrl = trimEmptyHash($browser.url());
      var newUrl = trimEmptyHash($location.absUrl());
      var oldState = $browser.state();
      var currentReplace = $location.$$replace;
      var urlOrStateChanged = oldUrl !== newUrl ||
        ($location.$$html5 && $sniffer.history && oldState !== $location.$$state);

      if (initializing || urlOrStateChanged) {
        initializing = false;

        $rootScope.$evalAsync(function() {
          var newUrl = $location.absUrl();
          var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
              $location.$$state, oldState).defaultPrevented;

          // if the location was changed by a `$locationChangeStart` handler then stop
          // processing this location change
          if ($location.absUrl() !== newUrl) return;

          if (defaultPrevented) {
            $location.$$parse(oldUrl);
            $location.$$state = oldState;
          } else {
            if (urlOrStateChanged) {
              setBrowserUrlWithFallback(newUrl, currentReplace,
                                        oldState === $location.$$state ? null : $location.$$state);
            }
            afterLocationChange(oldUrl, oldState);
          }
        });
      } 

And as urlOrStateChanged is true, the function afterLocationChange is called which makes rul rechange.

@NeoGenet1c
Copy link
Author

@chaserstrong Yep. Christopher's solution worked for me.

@christopherthielen
Copy link
Contributor

This plunker demonstrates the issue:
http://plnkr.co/edit/3arZH4m0iuaUlVBw06So?p=preview

@christopherthielen
Copy link
Contributor

This will be fixed in rc.1: http://plnkr.co/edit/Xu8FkXNkAFvD6BEJsHvR?p=preview

Note: .otherwise() doesn't use { replace: true } by default, which is another potential issue but would be a breaking change.

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

No branches or pull requests

3 participants