79

Is it possible to detect that a user entered a page through using the history back button in his browser? Preferably I want to detect this action using angular.js.

I do not want to use angular routing. It should also work if a user submits a form and after a successful submit to the server and a re-direct, it should also be possible if the user goes back to the form using the back button of the browser.

8 Answers 8

120

Here is a solution with Angular.

myApp.run(function($rootScope, $route, $location){
   //Bind the `$locationChangeSuccess` event on the rootScope, so that we dont need to 
   //bind in induvidual controllers.

   $rootScope.$on('$locationChangeSuccess', function() {
        $rootScope.actualLocation = $location.path();
    });        

   $rootScope.$watch(function () {return $location.path()}, function (newLocation, oldLocation) {
        if($rootScope.actualLocation === newLocation) {
            alert('Why did you use history back?');
        }
    });
});

I am using a run block to kickstart this. First I store the actual location in $rootScope.actualLocation, then I listen to $locationChangeSuccess and when it happens I update actualLocation with the new value.

In the $rootScope I watch for changes in the location path and if the new location is equal to previousLocation is because the $locationChangeSuccess was not fired, meaning the user has used the history back.

Sign up to request clarification or add additional context in comments.

12 Comments

Thanks! Looks great. Have to check it in detail tomorrow. Especially I have to check whether the $rootScope.actualLocation is also updated if the location is changed without using angular routes, but "normal" hyperlinks. Do you think it will work also using normal hyperlinks?
ah yes. never used angular routes before, so looking at your example it seems as there are no normal and not normal (angular route type) hyperlinks. so just forget my comment..
The $locationProvider.html5Mode(true) is only used to make links work in jsFiddle, you won't need it in your project. Just remember the links need to be preceded with #/ so angular can capture them (in the fiddle they are not). If you use a "normal" link, something like /book, angular won't capture it and the page will be redirected to that url, this will reset your app because all scripts will be reloaded, that is why it won't work.
For anybody (like me) who doesn't understand how it works: When url changing in usual way (not using back/forward button), then $locationChangeSuccess event firing after $watch callback. But when url changing using back/forward button or history.back() api, then $locationChangeSuccess event firing before $watch callback. So when watch callback firing actualLocation is already equals to newLocation. It is just how angular works internally and this solution just uses this internal behaviour.
The code above does not detect URL query string changes, like from /#/news/?page=2 to /#/news/?page=3. To catch those too you can use $location.absUrl() instead of $location.path() (in both places where it's used above).
|
12

If you want more precise solution (detecting back and forward) I extended solution delivered by Bertrand:

$rootScope.$on('$locationChangeSuccess', function() {
    $rootScope.actualLocation = $location.path();
});


$rootScope.$watch(function () {return $location.path()}, function (newLocation, oldLocation) {

    //true only for onPopState
    if($rootScope.actualLocation === newLocation) {

        var back,
            historyState = $window.history.state;

        back = !!(historyState && historyState.position <= $rootScope.stackPosition);

        if (back) {
            //back button
            $rootScope.stackPosition--;
        } else {
            //forward button
            $rootScope.stackPosition++;
        }

    } else {
        //normal-way change of page (via link click)

        if ($route.current) {

            $window.history.replaceState({
                position: $rootScope.stackPosition
            });

            $rootScope.stackPosition++;

        }

    }

 });

Comments

7

For ui-routing, I'm using the below code.

Inside App.run(), use

 $rootScope.$on("$stateChangeStart", function (event, toState, toParams, fromState, fromParams) 
 {

    if (toState.name === $rootScope.previousState )
       { 
          // u can any 1 or all of below 3 statements
         event.preventDefault();  // to Disable the event
         $state.go('someDefaultState'); //As some popular banking sites are using 
         alert("Back button Clicked");

       }
 else
      $rootScope.previousState= fromState.name;
   });

You can get the 'previousState' value in '$stateChangeSuccess' event also like as follows if `you want.

    $rootScope.$on("$stateChangeSuccess", function (event, toState, toParams, fromState, fromParams) 
   {

        $rootScope.previousStatus = fromState.name;
    });

1 Comment

This doesn't detect back button clicks -- this just detects if the user is toggling between two state names -- either via back button, or by clicking between two pages. Also this doesn't look at the params of the state.
1

I know it is an old question. Anyways :)

Bema's answer looks great. However, if you want to make it work with 'normal' urls (without hash I guess), you could compare absolute path, instead of the one returned by $location.path():

myApp.run(function($rootScope, $route, $location){
//Bind the `$locationChangeSuccess` event on the rootScope, so that we dont need to 
//bind in induvidual controllers.

    $rootScope.$on('$locationChangeSuccess', function() {
        $rootScope.actualLocation = $location.absUrl();
    });      

    $rootScope.$watch(function () {return $location.absUrl()}, function (newLocation, oldLocation) {
        if($rootScope.actualLocation === newLocation) {
            alert('Why did you use history back?');
        }
    });
});

Comments

0

JavaScript has a native history object.

window.history

Check the MDN for more info; https://developer.mozilla.org/en-US/docs/DOM/window.history?redirectlocale=en-US&redirectslug=window.history

Not sure how good it's on multi-browser support tho.

Update

Seems what I've called above is only for Gecko-type browsers.

For other browsers try to use history.js; https://github.com/browserstate/history.js

1 Comment

Thanks for your comment. As I'm preferring a way to do it in angular I will give Bertrands answer a try.
0

So, I've transcribed the answer by @Bema into TypeScript, and this is what it looks like:

namespace MyAwesomeApp {
    // ReSharper disable once Class
    function detectBackButton(
        $rootScope: ng.IRootScopeService,
        $location: ng.ILocationService
    ) {
        let actualLocation: string = '';

        $rootScope.$on('$locationChangeSuccess',
            () => {
                actualLocation = $location.path();
            });

        $rootScope.$watch(() => $location.path(),
            (newLocation: string, oldLocation: string) => {
                if (actualLocation === newLocation) {
                    //$rootScope.$broadcast('onBackButtonPressed', null);
                }
            });
    }

    detectBackButton.$inject = [
        '$rootScope',
        '$location'
    ];

    angular
        .module('app')
        .run(detectBackButton);
}

We don't have to create a property off of the $rootScope service, we can just have our 'on location change success' and 'on location changed' code both close over the local actualLocation variable. From there, you can do whatever you like, just as in the original code. For my part, I'd consider broadcasting an event so that individual controllers can do whatever they must, but you could include global actions if you had to.

Thanks for the great answer, and I hope this helps other typescript users out there.

Comments

-2

My Solutio to this problem is

app.run(function($rootScope, $location) {
    $rootScope.$on('$locationChangeSuccess', function() {
        if($rootScope.previousLocation == $location.path()) {
            console.log("Back Button Pressed");
        }
        $rootScope.previousLocation = $rootScope.actualLocation;
        $rootScope.actualLocation = $location.path();
    });
});

Comments

-7

when pressing back button, angular fires only $routeChangeStart event, $locationChangeStart not fired.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.