Skip to content

Access previous state #92

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
kloy opened this issue Apr 17, 2013 · 66 comments
Closed

Access previous state #92

kloy opened this issue Apr 17, 2013 · 66 comments
Labels

Comments

@kloy
Copy link

kloy commented Apr 17, 2013

To my knowledge this is not supported. It would be very helpful to be able to know the previous state, even if it is just the state's name. This information is useful for when you need to do logic based on which state you reached the current state from.

@jeme
Copy link
Contributor

jeme commented Apr 17, 2013

Where do you want access to this?

There is a number of ways you can make this available right now, it's a bit of a workaround though, but should work. In all cases you should listen to "$stateChangeSuccess" and store the parameters from that state, in your case it's specifically the previous state your interested in, so store that. You can do this by:

  1. Create a service, listen to the event and when triggered set a service value which you then will make available from the service.
  2. Create a PageController, listen to the event and when triggered set previousState on it's scope (or root scope).
  3. Use run on module, listen to the event and when triggered set previousState on the rootscope

E.g.:

angular.module('sample')
    .run( [ '$rootScope', function ($rootScope) {
        $rootScope.$on('$stateChangeSuccess', function(event, to, toParams, from, fromParams) {
            $rootScope.$previousState = from;
        });
      }]);

@kloy
Copy link
Author

kloy commented Apr 22, 2013

@jeme I understand you could do it this way, however I do not wish to add properties to the $rootScope that are not meant to be used in the template. I could also create a service as mentioned, but then I am creating a service to hold a single property.

The way I would prefer for it to work for a property to be added $state so then something like if ($state.current.name === $state.previous.name) could be used.

@timkindberg
Copy link
Contributor

Use @jeme method above. Will reconsider if more devs request this functionality.

@ADmad
Copy link

ADmad commented Oct 1, 2013

👍 to this feature.

@rosslavery
Copy link

+1 to this also.

Example use case would be if the user is not authorized to view a state, they will be forwarded to /login.

If we could access the previous $state in the LoginCtrl we could easily forward them to the state they were attempting to reach after they successfully log in.

For now I can place it on $rootScope but since @timkindberg was gauging interest, I thought I'd add my $0.02.

@ADmad
Copy link

ADmad commented Oct 3, 2013

@rabhw My use case is similar to what you describe and I am sure many others too.

@deltaepsilon
Copy link
Contributor

+1. @jeme 's method works fine, but I was surprised it wasn't already implemented natively.

@xmlking
Copy link

xmlking commented Oct 18, 2013

+1

@TwoToneBytes
Copy link

Definitely would love access a previous state. I think having it saved off in the rootscope is pretty smelly.

@santialbo
Copy link

I request this functionality too :)

@sebastianzillessen
Copy link

+1

@Cinamonas
Copy link

Not to pollute $rootScope, I am manually setting previous state on $state object:

app.run(function ($rootScope, $state) {
  $rootScope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState) {
    $state.previous = fromState;
  });
});

@timkindberg
Copy link
Contributor

Someone who's +1'd it should consider contributing a PR for this. Fairly
low risk, good opportunity to participate. @nateabele are you against this
feature, or just don't have time for it? If you are not against it, what
would be a couple words of wisdom for anyone willing to implement it?

@xmlking
Copy link

xmlking commented Nov 20, 2013

I had to use back button on multiple locations. My generic solution is:

app.js

    angular.module('ConsoleUIApp', ['ui.router','ui.bootstrap'])
        .config(function ($stateProvider, $urlRouterProvider, $httpProvider) {

            // For any unmatched url, redirect to /state1
            $urlRouterProvider.otherwise("/home");

            $stateProvider
                .state('home', {
                    url: "/home",
                    templateUrl: "views/home.html",
                    controller: 'HomeCtrl'
                })
                .state('testing', {
                    url: "/testing",
                    templateUrl: "views/testing.html",
                    controller: 'TestingCtrl'
                })
        })

        .run(function($rootScope, growl, $state, $stateParams) {
            $rootScope.$state = $state;
            $rootScope.$stateParams = $stateParams;
            $rootScope.$on("$stateChangeSuccess",  function(event, toState, toParams, fromState, fromParams) {
                // to be used for back button //won't work when page is reloaded.
                $rootScope.previousState_name = fromState.name;
                $rootScope.previousState_params = fromParams;
            });
            //back button function called from back button's ng-click="back()"
            $rootScope.back = function() {
                $state.go($rootScope.previousState_name,$rootScope.previousState_params);
            };
        });

HTML

 <div class="panel panel-default">
  <!-- Default panel contents -->
  <div class="panel-heading">Environment  Permissions
      <div class="btn-group pull-right">
          <i class="btn btn-default glyphicon glyphicon-plus" ng-click="addPermission(1)"></i>
          <i class="btn btn-default glyphicon glyphicon-circle-arrow-left" ng-click="back()"></i>
      </div>
      <div class="clearfix"></div>
  </div>
  <div class="panel-body">
    <p> You can add remove Permissions </p>
  </div>
</div>

@nateabele
Copy link
Contributor

I plan on implementing it as part of the $transition$ service, which isn't fully fleshed out yet, but if someone's willing to step up to the plate on this, I'll work with them and try to give a little guidance. /cc @timkindberg

@ShimmerGlass
Copy link

I wrote this directive based on @xmlking comment, it adds support for default state when no previous one is available.

In your .run() :

// previous state handling
$rootScope.previousState = {};
$rootScope.$on('$stateChangeSuccess',  function(event, toState, toParams, fromState, fromParams) {
    // store previous state in $rootScope
    $rootScope.previousState.name = fromState.name;
    $rootScope.previousState.params = fromParams;
});

back-button directive :

yourModule.directive('backButton', [
    '$rootScope',
    '$state',
    '$parse',
function($rootScope, $state, $parse) {
    return {
        restrict: 'EA',
        link: function(scope, el, attrs) {
            var defaultState
              , defaultStateParams;

            el.click(function() {
                var stateName
                  , stateParams;

                if ($rootScope.previousState.name) {
                    stateName = $rootScope.previousState.name;
                    stateParams = $rootScope.previousState.params;
                }
                else {
                    stateName = defaultState;
                    stateParams = defaultStateParams;
                }

                if (stateName)
                    $state.go(stateName, stateParams);
            });

            attrs.$observe('defaultState', function() {
                defaultState = attrs.defaultState;
            });
            attrs.$observe('defaultStateParams', function() {
                defaultStateParams = $parse(attrs.defaultStateParams)(scope);
            });

            $rootScope.$watch('previousState', function(val) {
                el.attr('disabled', !val.name && !defaultState);
            });
        }
    };
}]);

@coderigo
Copy link

+1. My use case is opening up a help modal via a route. When the modal closes I'd like to return to the route the user was in before the modal was opened, which could be any route in my app.

@pleerock
Copy link

pleerock commented Jan 9, 2014

@Aestek this solution does not work if your directly go to the page where this button is

@xmlking
Copy link

xmlking commented Jan 10, 2014

ya, also don't work if you reload the page. you many want to store previousState in browser LocalStore

@pleerock
Copy link

I think its better if we explicitly set route in the button like back-button="state.that.comes.berore.this.page", this should work like ui-sref, but also implement correct history behavior

@austencollins
Copy link

+1

@ShimmerGlass
Copy link

@pleerock, I agree with you. I was thinking of something like ui-sref="$back" and ui-sref="$forward"with theses special states directly handled by ui-router. As you say, a complete history management is needed, here if you keep hitting the back-button, you will just loop through 2 states.
Something like ui-sref="$back(default_state_if_none_available)" should be enough if the visitor is already on the first page IMO.

mike-tempest pushed a commit to mike-tempest/ui-router that referenced this issue Feb 4, 2014
Adds $state.previous and $state.previousParams. Implements $state.back method
which wraps $state.transitionTo and can take an options object which is passed
through.

$state.previous and $state.previousParams are only updated when the transition
completes.
@mike-tempest
Copy link

Hey guys, we have done a pull request which fixes this issue adding a $state.back method which also sends all params automatically through to transitionTo from your previous state.

#861

@TimothyKrell
Copy link

Sweet! Thank you! Will this make it into the next release?

@mike-tempest
Copy link

Hopefully, we will see how the authors like the solution, at the moment they seem to think it should be $location issue. However, if you do not want to update urls for several reasons, $state is actually the best place for it to be in my opinion.

mike-tempest pushed a commit to mike-tempest/ui-router that referenced this issue Feb 5, 2014
Adds $state.previous and $state.previousParams. Implements $state.back method
which wraps $state.transitionTo and can take an options object which is passed
through.

$state.previous and $state.previousParams are only updated when the transition
completes.

Added unit tests for .back method()
@CMCDragonkai
Copy link

Has there be any work on this? This would be useful when you have a modal state that could exist as child state of many parent states, and when closing the modal state, you want to go back to the original parent state.

As a side note, this could mean that the modal urls could be inherited from any parent url. How would one manage multiple inheritance of a particular state?

@christopherthielen
Copy link
Contributor

I am curious why not use the browser's built-in back/forward capability?

If I understand the PR correctly, when transitionTo is used, the browser history gets the "back" state's URL appended to the history, and so the back button doesn't behave as a user might expect.

@homerjam
Copy link

homerjam commented Apr 4, 2014

+1

@Cinamonas solution seems like a good work around for the time being:

app.run(function ($rootScope, $state) {
  $rootScope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState) {
    $state.previous = fromState;
  });
});

My use case is pretty simple — I have a state/page which is accessible from multiple other states/pages however I want to conditionally show the correct label on the back button depending on the route in to said state.

I'd like to contribute on this, will try to find time to so.

@rfink
Copy link

rfink commented Apr 25, 2014

+1

@gigablox
Copy link

gigablox commented May 6, 2014

+1 Many cases I can see this being useful for.

@nateabele
Copy link
Contributor

This is coming soon.

@nateabele nateabele reopened this May 13, 2014
@mike-tempest
Copy link

Thank you for reopening! Knew it would be a more popular request when Angular' popularity grew.

@nateabele
Copy link
Contributor

Aggregated +1 count: 11 -- okay everyone, I think that's enough of them. ;-)

@IainCole
Copy link

+1..... (sorry I couldn't help myself)

@rpedroni
Copy link

rpedroni commented Jun 1, 2014

+1 (Since all the young kids were doing it)

@furixturi
Copy link

+1 Confronted by my use case today

@jough
Copy link

jough commented Jun 30, 2014

+1, since this is still open.

@fungiboletus
Copy link

+1, ui-sref="<" is more beautiful than ng-href="{{previousState}}"

@christopherthielen
Copy link
Contributor

This is coming soon.

@nateabele Have you started writing code for this? I am thinking about taking a stab at the $history service and couldn't find a branch where this was started.


Also, here's a simple $previousState service I wrote for ui-router-extras that might be useful in the meantime. I'm using it on the demo site to transition back out of a modal: http://bit.ly/1pQe1jv

It has get([optional:memoName]), go([optional:memoName]) and memo([memoName])

angular.module('ct.ui.router.extras').service("$previousState", 
    [ '$rootScope', '$state', 
function($rootScope, $state) {
  var previous = null;
  var memos = {};

  var lastPrevious = null;

  $rootScope.$on("$stateChangeStart", function(evt, toState, toStateParams, fromState, fromStateParams) {
    lastPrevious = previous;
    previous = { state: fromState, params: fromStateParams };
  });

  $rootScope.$on("$stateChangeError", function() {
    previous = lastPrevious;
    lastPrevious = null;
  });

  $rootScope.$on("$stateChangeSuccess", function() {
    lastPrevious = null;
  });

  var $previousState = {
    get: function(memoName) {
      return memoName ? memos[memoName] : previous; 
    },
    go: function(memoName) {
      var to = $previousState.get(memoName);
      return $state.go(to.state, to.params)
    },
    memo: function(memoName) {
      memos[memoName] = previous; 
    }
  };

  return $previousState;
}]);

angular.module('ct.ui.router.extras').run(['$previousState', function($previousState) {
  "use strict";
  // Inject in .run so it can register $rootScope.$on.
}]);

@ee0pdt
Copy link

ee0pdt commented Jul 18, 2014

+1

2 similar comments
@tkalimov
Copy link

+1

@hung-phan
Copy link

+1

@nfantone
Copy link

+1. Needed this today and Google point me to this issue.

@gevgeny
Copy link

gevgeny commented Sep 3, 2014

+1

2 similar comments
@ygotthilf
Copy link

+1

@Phoscur
Copy link

Phoscur commented Sep 7, 2014

+1

@goleafs
Copy link

goleafs commented Sep 12, 2014

wow.. google for "ui-router back" and here I am to offer up another +1

@ghost
Copy link

ghost commented Sep 17, 2014

+1

2 similar comments
@mlegenhausen
Copy link

+1

@andrelevi
Copy link

+1

@arthurcavallari
Copy link

+1 - same use case as many others have mentioned regarding the authorization problem

@angular-ui angular-ui locked and limited conversation to collaborators Sep 18, 2014
@nateabele
Copy link
Contributor

Copying this here so I don't have to keep searching for it: http://plnkr.co/edit/DJH6mQUCbTFfSbdCBYUo?p=preview ($history service).

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

Successfully merging a pull request may close this issue.