Angular - ui-router get previous state
Asked Answered
I

14

158

Is there a way to get the previous state of the current state?

For example I would like to know what the previous state was before current state B (where previous state would have been state A).

I am not able to find it in ui-router github doc pages.

Insensible answered 19/5, 2013 at 13:55 Comment(1)
the answer below is correct you can also find all the info you need from the source if the docs aren't enough help goo.gl/9B9bHImpromptu
H
136

ui-router doesn't track the previous state once it transitions, but the event $stateChangeSuccess is broadcast on the $rootScope when the state changes.

You should be able to catch the prior state from that event (from is the state you're leaving):

$rootScope.$on('$stateChangeSuccess', function (ev, to, toParams, from, fromParams) {
   //assign the "from" parameter to something
});
Henrion answered 28/5, 2013 at 15:40 Comment(5)
What would be an example using "from"?Paez
Here 'to' and 'from' means 'toState' and 'fromState'. Consider your previous url is localhost:xxxx/employee and controller is 'EmployeesController' then the example for 'fromState' is : Object {url: "/employees", templateUrl: "/employees", controller: "EmployeesController", name: "employees"}Obloquy
I did this in my abstract: ` $rootScope.previousState; $rootScope.currentState; $rootScope.$on('$stateChangeSuccess', function(ev, to, toParams, from, fromParams) { $rootScope.previousState = from.name; $rootScope.currentState = to.name; console.log('Previous state:'+$rootScope.previousState) console.log('Current state:'+$rootScope.currentState) }); ` It keep track of previous and current in rootScope. Pretty handy!Rawhide
Using solution of @endy-tjahjono (https://mcmap.net/q/150627/-angular-ui-router-get-previous-state) is more inline of ui-router 1.x.Analogize
I love a simple way with history.back() <a href="javascript:history.back()" class="btn btn-default no-radius">Anchie
P
153

I use resolve to save the current state data before moving to the new state:

angular.module('MyModule')
.config(['$stateProvider', function ($stateProvider) {
    $stateProvider
        .state('mystate', {
            templateUrl: 'mytemplate.html',
            controller: ["PreviousState", function (PreviousState) {
                if (PreviousState.Name == "mystate") {
                    // ...
                }
            }],
            resolve: {
                PreviousState: ["$state", function ($state) {
                    var currentStateData = {
                        Name: $state.current.name,
                        Params: angular.copy($state.params),
                        URL: $state.href($state.current.name, $state.params)
                    };
                    return currentStateData;
                }]
            }
        });
}]);
Paella answered 20/9, 2014 at 3:51 Comment(12)
much better than dealing with $stateChangeSuccessCronin
In my opinion, this should be the accepted answer. Event though the $stateChangeSuccess works, it does it on a global level, which isn't really needed most of the time.Komatik
I agree. This is much cleaner. If you're dealing with many modules that all have states, $stateChangeSuccess will be fired for every state change, not just the ones in your module. Another vote for this being the better solution.Cythera
This is better than $stateChangeSuccess solution EXCEPT if you already have implemented $stateChangeSuccess for any other reason. Then you can get previous state with only one line of code. Upvote for both answers :)Amaya
notice the case that you do not transition from another state, but user directly navigates to your state's url, $state.current will be nullMissie
@Nissassin17 which version do you use? In v0.2.15 when opening a state directly the $state.current is object: {name: "", url: "^", views: null, abstract: true}.Paella
@EndyTjahjono: oh, I'm sorry. It was my mistake. I've tested and it returned an object same as your {name: "", url: "^", views: null, abstract: true}Missie
@EndyTjahjono: Can you please clarify how can we use "PreviousState.Name" in any other controller ? I tried injecting it but got empty stringCoriolanus
FYI - I needed to do the following: Params: angular.copy($state.params) - Explanation: I'm using UI-Router v 1.0.0-beta 2 and i was having issues with the Params portion of this code; because $state.params is an object, it will update to the current view after the function is resolved... i needed to do this in the resolve function to prevent the object from being updated in the current view.Resile
Thank you for your sollution. As I use 1.0.0-beta.3 version. your approach works. I confirm as @JoeH said you need to angular.copy($state.params) because js sets reference to object instead, so when you access it inside your component or controller $state will be changed to your current state object, and you'll lose your params from previous one.Supernal
This approach doesn't handle using the back button for some reason.Moral
Good but i would change this up a little var currentStateData = { Name: $state.current.name, Params: {...$state.params}, URL: $state.href($state.current.name, $state.params) };Venipuncture
H
136

ui-router doesn't track the previous state once it transitions, but the event $stateChangeSuccess is broadcast on the $rootScope when the state changes.

You should be able to catch the prior state from that event (from is the state you're leaving):

$rootScope.$on('$stateChangeSuccess', function (ev, to, toParams, from, fromParams) {
   //assign the "from" parameter to something
});
Henrion answered 28/5, 2013 at 15:40 Comment(5)
What would be an example using "from"?Paez
Here 'to' and 'from' means 'toState' and 'fromState'. Consider your previous url is localhost:xxxx/employee and controller is 'EmployeesController' then the example for 'fromState' is : Object {url: "/employees", templateUrl: "/employees", controller: "EmployeesController", name: "employees"}Obloquy
I did this in my abstract: ` $rootScope.previousState; $rootScope.currentState; $rootScope.$on('$stateChangeSuccess', function(ev, to, toParams, from, fromParams) { $rootScope.previousState = from.name; $rootScope.currentState = to.name; console.log('Previous state:'+$rootScope.previousState) console.log('Current state:'+$rootScope.currentState) }); ` It keep track of previous and current in rootScope. Pretty handy!Rawhide
Using solution of @endy-tjahjono (https://mcmap.net/q/150627/-angular-ui-router-get-previous-state) is more inline of ui-router 1.x.Analogize
I love a simple way with history.back() <a href="javascript:history.back()" class="btn btn-default no-radius">Anchie
R
100

For sake of readability, I'll place my solution (based of stu.salsbury's anwser) here.

Add this code to your app's abstract template so it runs on every page.

$rootScope.previousState;
$rootScope.currentState;
$rootScope.$on('$stateChangeSuccess', function(ev, to, toParams, from, fromParams) {
    $rootScope.previousState = from.name;
    $rootScope.currentState = to.name;
    console.log('Previous state:'+$rootScope.previousState)
    console.log('Current state:'+$rootScope.currentState)
});

Keeps track of the changes in rootScope. Its pretty handy.

Rawhide answered 29/4, 2014 at 3:47 Comment(3)
It's worthwhile to store the fromParams as well if you really want to redirect someone back to where they came from.Typesetter
I love this and implement it in my applications. ThanksDion
Really useful... Even more using localStorage, you can get the previousState anywhere.Downcomer
T
14

In the following example i created a decorator (runs only once per app at configuration phase) and adds an extra property to $state service, so this approach does not add global variables to $rootscope and does not require to add any extra dependency to other services than $state.

In my example i needed to redirect a user to the index page when he was already signed in and when he was not to redirect him to the previous "protected" page after sign in.

The only unknown services (for you) that i use are authenticationFactory and appSettings:

  • authenticationFactory just administrates the user login. In this case i use only a method to identify if the user is logged in or not.
  • appSettings are constants just for not use strings everywhere. appSettings.states.login and appSettings.states.register contain the name of the state for the login and register url.

Then in any controller/service etc you need to inject $state service and you can access current and previous url like this:

  • Current: $state.current.name
  • Previous: $state.previous.route.name

From the Chrome console:

var injector = angular.element(document.body).injector();
var $state = injector.get("$state");
$state.current.name;
$state.previous.route.name;

Implementation:

(I'm using angular-ui-router v0.2.17 and angularjs v1.4.9)

(function(angular) {
    "use strict";

    function $stateDecorator($delegate, $injector, $rootScope, appSettings) {
        function decorated$State() {
            var $state = $delegate;
            $state.previous = undefined;
            $rootScope.$on("$stateChangeSuccess", function (ev, to, toParams, from, fromParams) {
                $state.previous = { route: from, routeParams: fromParams }
            });

            $rootScope.$on("$stateChangeStart", function (event, toState/*, toParams, fromState, fromParams*/) {
                var authenticationFactory = $injector.get("authenticationFactory");
                if ((toState.name === appSettings.states.login || toState.name === appSettings.states.register) && authenticationFactory.isUserLoggedIn()) {
                    event.preventDefault();
                    $state.go(appSettings.states.index);
                }
            });

            return $state;
        }

        return decorated$State();
    }

    $stateDecorator.$inject = ["$delegate", "$injector", "$rootScope", "appSettings"];

    angular
        .module("app.core")
        .decorator("$state", $stateDecorator);
})(angular);
Teamwork answered 4/2, 2016 at 17:41 Comment(0)
B
12

Add a new property called {previous} to $state on $stateChangeStart

$rootScope.$on( '$stateChangeStart', ( event, to, toParams, from, fromParams ) => {
    // Add {fromParams} to {from}
    from.params = fromParams;

    // Assign {from} to {previous} in $state
    $state.previous = from;
    ...
}

Now anywhere you need can use $state you will have previous available

previous:Object
    name:"route name"
    params:Object
        someParam:"someValue"
    resolve:Object
    template:"route template"
    url:"/route path/:someParam"

And use it like so:

$state.go( $state.previous.name, $state.previous.params );
Beefeater answered 9/6, 2016 at 0:56 Comment(0)
P
9

I am stuck with same issue and find the easiest way to do this...

//Html
<button type="button" onclick="history.back()">Back</button>

OR

//Html
<button type="button" ng-click="goBack()">Back</button>

//JS
$scope.goBack = function() {
  window.history.back();
};

(If you want it to be more testable, inject the $window service into your controller and use $window.history.back()).

Persse answered 27/1, 2017 at 12:9 Comment(4)
it is more complicated when you have menu in your applicationMoyer
not getting your problem please provide more detailPersse
You will lost $stateParams if you do not push it into url, the better solution is save your previous params in $rootScope, you can get it from that and push into the $state to go back.Merriweather
This answer is easiest for me to implement.Came
D
8

I use a similar approach to what Endy Tjahjono does.

What I do is to save the value of the current state before making a transition. Lets see on an example; imagine this inside a function executed when cliking to whatever triggers the transition:

$state.go( 'state-whatever', { previousState : { name : $state.current.name } }, {} );

The key here is the params object (a map of the parameters that will be sent to the state) -> { previousState : { name : $state.current.name } }

Note: note that Im only "saving" the name attribute of the $state object, because is the only thing I need for save the state. But we could have the whole state object.

Then, state "whatever" got defined like this:

.state( 'user-edit', {
  url : 'whatever'
  templateUrl : 'whatever',
  controller: 'whateverController as whateverController',
  params : {
    previousState: null,
  }
});

Here, the key point is the params object.

params : {
  previousState: null,
}

Then, inside that state, we can get the previous state like this:

$state.params.previousState.name
Delve answered 11/6, 2015 at 9:27 Comment(0)
T
6

Here is a really elegant solution from Chris Thielen ui-router-extras: $previousState

var previous = $previousState.get(); //Gets a reference to the previous state.

previous is an object that looks like: { state: fromState, params: fromParams } where fromState is the previous state and fromParams is the previous state parameters.

Thessalonians answered 21/10, 2015 at 6:55 Comment(1)
On May 16, 2017, Chris Thielen added an End of Life Notice for the project: ui-router-extras.Cosentino
S
4

Ok, I know that I am late to the party here, but I am new to angular. I am trying to make this fit into the John Papa style guide here. I wanted to make this reusable so I created in a block. Here is what I came up with:

previousStateProvider

(function () {
'use strict';

angular.module('blocks.previousState')
       .provider('previousState', previousStateProvider);

previousStateProvider.$inject = ['$rootScopeProvider'];

function previousStateProvider($rootScopeProvider) {
    this.$get = PreviousState;

    PreviousState.$inject = ['$rootScope'];

    /* @ngInject */
    function PreviousState($rootScope) {
        $rootScope.previousParms;
        $rootScope.previousState;
        $rootScope.currentState;

        $rootScope.$on('$stateChangeSuccess', function (ev, to, toParams, from, fromParams) {
            $rootScope.previousParms = fromParams;
            $rootScope.previousState = from.name;
            $rootScope.currentState = to.name;
        });
    }
}
})();

core.module

(function () {
'use strict';

angular.module('myApp.Core', [
    // Angular modules 
    'ngMessages',
    'ngResource',

    // Custom modules 
    'blocks.previousState',
    'blocks.router'

    // 3rd Party Modules
]);
})();

core.config

(function () {
'use strict';

var core = angular.module('myApp.Core');

core.run(appRun);

function appRun(previousState) {
    // do nothing. just instantiating the state handler
}
})();

Any critique on this code will only help me, so please let me know where I can improve this code.

Skiff answered 13/9, 2016 at 3:47 Comment(0)
S
2

If you just need this functionality and want to use it in more than one controller, this is a simple service to track route history:

  (function () {
  'use strict';

  angular
    .module('core')
    .factory('RouterTracker', RouterTracker);

  function RouterTracker($rootScope) {

    var routeHistory = [];
    var service = {
      getRouteHistory: getRouteHistory
    };

    $rootScope.$on('$stateChangeSuccess', function (ev, to, toParams, from, fromParams) {
      routeHistory.push({route: from, routeParams: fromParams});
    });

    function getRouteHistory() {
      return routeHistory;
    }

    return service;
  }
})();

where the 'core' in .module('core') would be the name of your app/module. Require the service as a dependency to your controller, then in your controller you can do: $scope.routeHistory = RouterTracker.getRouteHistory()

Sherlene answered 6/11, 2015 at 20:31 Comment(1)
If I had a navigational button on my page that goes to the previous state in the history, after navigating to that prior state, stateChangeSuccess would be fired which adds it to the history. So wouldn't this code end up resulting in an endless loop of going back and forth between 2 pages?Ichnite
U
1

I keep track of previous states in $rootScope, so whenever in need I will just call the below line of code.

$state.go($rootScope.previousState);

In App.js:

$rootScope.$on('$stateChangeSuccess', function(event, to, toParams, from, fromParams) {
  $rootScope.previousState = from.name;
});
Unfeigned answered 22/2, 2018 at 8:41 Comment(1)
I think it's better if you save from not just from.name. That way, you will have the params as well, in case you need to do something like $state.go($tootScope.previousState.name, $tootScope.previousState.params);Finicky
T
0

A really simple solution is just to edit the $state.current.name string and cut out everything including and after the last '.' - you get the name of the parent state. This doesn't work if you jump a lot between states because it just parses back the current path. But if your states correspond to where you actually are, then this works.

var previousState = $state.current.name.substring(0, $state.current.name.lastIndexOf('.'))
$state.go(previousState)
Tailpipe answered 28/5, 2016 at 10:11 Comment(2)
$state.go('^') will accomplish thisVinegarette
And what about the state params?Airsickness
C
0

For UI-Router(>=1.0), StateChange events have been deprecated. For complete migration guide, click here

To get the previous state of the current state in UI-Router 1.0+:

app.run(function ($transitions) {
    $transitions.onSuccess({}, function (trans) {
         // previous state and paramaters
         var previousState = trans.from().name;
         var previousStateParameters = trans.params('from');
    });
});
Cru answered 26/3, 2019 at 10:19 Comment(0)
C
-2

You can return the state this way:

$state.go($state.$current.parent.self.name, $state.params);

An example:

(function() {
    'use strict'

    angular.module('app')
        .run(Run);

    /* @ngInject */
    function Run($rootScope, $state) {

        $rootScope.back = function() {
            $state.go($state.$current.parent.self.name, $state.params);
        };

    };

})();
Cryometer answered 3/2, 2016 at 19:23 Comment(2)
That's not a good answer if you're accessing the state from a non-parent state. If you just want the parent of current state, it does the job.Bonsai
Isn't this the same as using $state.go("^")?Warfeld

© 2022 - 2024 — McMap. All rights reserved.