¿Cómo detectar el evento de clic del botón de retroceso del navegador usando angular?

79

¿Es posible detectar que un usuario ingresó a una página usando el botón de retroceso del historial en su navegador? Preferiblemente quiero detectar esta acción usando angular.js.

No quiero usar enrutamiento angular. También debería funcionar si un usuario envía un formulario y después de un envío exitoso al servidor y una redirección, también debería ser posible si el usuario vuelve al formulario usando el botón Atrás del navegador.

Thomas Kremmel
fuente

Respuestas:

120

Aquí hay una solución con 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?');
        }
    });
});

Estoy usando un bloque de ejecución para iniciar esto. Primero almaceno la ubicación real en $ rootScope.actualLocation, luego escucho $ locationChangeSuccess y cuando sucede actualizo actualLocation con el nuevo valor.

En el $ rootScope observo cambios en la ruta de ubicación y si la nueva ubicación es igual a previousLocation es porque el $ locationChangeSuccess no se disparó, lo que significa que el usuario ha usado el historial nuevamente.

Bertrand
fuente
1
¡Gracias! Se ve muy bien. Mañana tengo que comprobarlo en detalle. Especialmente tengo que verificar si $ rootScope.actualLocation también se actualiza si la ubicación se cambia sin usar rutas angulares, sino hipervínculos "normales". ¿Crees que funcionará también con hipervínculos normales?
Thomas Kremmel
4
Ah, sí. Nunca usé rutas angulares antes, por lo que, mirando su ejemplo, parece que no hay hipervínculos normales y no normales (tipo de ruta angular). así que olvídate de mi comentario ..
Thomas Kremmel
2
El $ locationProvider.html5Mode (true) solo se usa para hacer que los enlaces funcionen en jsFiddle, no lo necesitará en su proyecto. Solo recuerde que los enlaces deben ir precedidos de # / para que angular pueda capturarlos (en el violín no lo son). Si usa un enlace "normal", algo como / book, angular no lo capturará y la página será redirigida a esa url, esto restablecerá su aplicación porque todos los scripts se volverán a cargar, por eso no funcionará .
Bertrand
19
Para cualquiera (como yo) que no entienda cómo funciona: cuando la URL cambia de la manera habitual (sin usar el botón de retroceso / avance), el $locationChangeSuccessevento se activa después de la $watch devolución de llamada. Pero cuando la URL cambia con el botón Atrás / Adelante o la history.back()API, el $locationChangeSuccessevento se activa antes de la $watch devolución de llamada. Entonces, cuando el disparo de devolución de llamada actualLocationya es igual a newLocation. Es cómo angular funciona internamente y esta solución solo usa este comportamiento interno.
Ruslan Stelmachenko
5
El código anterior no detecta cambios en la cadena de consulta de URL, como de /#/news/?page=2a /#/news/?page=3. Para capturarlos también puede usar en $location.absUrl()lugar de $location.path()(en ambos lugares donde se usa arriba).
Michael
11

Si desea una solución más precisa (detección hacia atrás y hacia adelante), la solución extendida entregada por 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++;

        }

    }

 });
Mariusz
fuente
8

Para el enrutamiento de interfaz de usuario, estoy usando el siguiente código.

Dentro App.run(), usa

 $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;
   });

Puede obtener el valor 'previousState' en el evento '$ stateChangeSuccess' también de la siguiente manera si desea.

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

        $rootScope.previousStatus = fromState.name;
    });
harish polanki
fuente
Esto no detecta los clics en el botón Atrás, solo detecta si el usuario está alternando entre dos nombres de estado, ya sea a través del botón Atrás o haciendo clic entre dos páginas. Además, esto no mira los parámetros del estado.
Amy B
1

Sé que es una vieja pregunta. De todos modos :)

La respuesta de Bema se ve muy bien. Sin embargo, si desea que funcione con URL 'normales' (sin hash, supongo), puede comparar la ruta absoluta, en lugar de la devuelta por $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?');
        }
    });
});
Jahongir Rahmonov
fuente
0

JavaScript tiene un objeto de historial nativo.

window.history

Consulte MDN para obtener más información; https://developer.mozilla.org/en-US/docs/DOM/window.history?redirectlocale=en-US&redirectslug=window.history

Aunque no estoy seguro de qué tan bueno es el soporte para múltiples navegadores.

Actualizar

Parece que lo que he llamado anteriormente es solo para navegadores tipo Gecko.

Para otros navegadores, intente utilizar history.js; https://github.com/browserstate/history.js


fuente
Gracias por tu comentario. Como prefiero una forma de hacerlo en angular, intentaré la respuesta de Bertrands.
Thomas Kremmel
0

Entonces, he transcrito la respuesta de @Bema a TypeScript, y así es como se ve:

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);
}

No tenemos que crear una propiedad fuera del $rootScopeservicio, solo podemos hacer que nuestro código 'cambio exitoso en la ubicación' y 'cambio en la ubicación' se cierren sobre la actualLocationvariable local . A partir de ahí, puede hacer lo que quiera, como en el código original. Por mi parte, consideraría retransmitir un evento para que los controladores individuales puedan hacer lo que sea necesario, pero podría incluir acciones globales si fuera necesario. .

Gracias por la gran respuesta, y espero que esto ayude a otros usuarios de mecanografiado.

Andrew Gray
fuente
-2

Mi solución a este problema es

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();
    });
});
syraz37
fuente
-7

al presionar el botón Atrás, angular dispara solo el evento $ routeChangeStart , $ locationChangeStart no dispara.

bullgare
fuente