AngularJs - cancelar evento de cambio de ruta


88

¿Cómo cancelo el evento de cambio de ruta en AngularJs?

Mi código actual es

$rootScope.$on("$routeChangeStart", function (event, next, current) {

// do some validation checks
if(validation checks fails){

    console.log("validation failed");

    window.history.back(); // Cancel Route Change and stay on current page  

}
});

con esto, incluso si la validación falla, Angular extrae la siguiente plantilla y los datos asociados y luego vuelve inmediatamente a la vista / ruta anterior. No quiero que angular extraiga la siguiente plantilla y datos si falla la validación, idealmente no debería haber window.history.back (). Incluso probé event.preventDefault () pero no utilicé.

Respuestas:


184

En lugar de $routeChangeStartusar$locationChangeStart

Aquí está la discusión al respecto de los chicos de angularjs: https://github.com/angular/angular.js/issues/2109

Editar 3/6/2018 Puede encontrarlo en los documentos: https://docs.angularjs.org/api/ng/service/$location#event-$locationChangeStart

Ejemplo:

$scope.$on('$locationChangeStart', function(event, next, current) {
    if ($scope.form.$invalid) {
       event.preventDefault();
    }
});

8
El problema con esto es que no hay forma de acceder a la colección de parámetros de ruta. Si está intentando validar los parámetros de ruta, esta solución no es buena.
KingOfHypocrites

1
Tenga en cuenta que cuando el uso de $routeChangeStartla nextvariable es solo una cadena y no puede contener ningún dato (por ejemplo, no puede acceder a la authorizedRolesvariable anterior definida )
MyTitle

@KingOfHypocrites no puede obtener parámetros de ruta, pero puede obtener $location.path()y$location.search()
Diseño de Adrian

2
¿Puede / es recomendable hacer esto en rootScope si desea realizar un seguimiento de todos los cambios de ruta? ¿O hay una alternativa más apetecible?
máximo

38

Una muestra de código más completa, usando $locationChangeStart

// assuming you have a module called app, with a 
angular.module('app')
  .controller(
    'MyRootController',
    function($scope, $location, $rootScope, $log) {
      // your controller initialization here ...
      $rootScope.$on("$locationChangeStart", function(event, next, current) { 
        $log.info("location changing to:" + next); 
      });
    }
  );

No estoy completamente satisfecho con conectar esto en mi controlador raíz (controlador de nivel superior). Si hay un patrón mejor, me encantaría saberlo. Soy nuevo en angular :-)


Esto funcionó muy bien para mí, aunque no estoy tratando de cancelar mi cambio de ruta como el póster original. ¡Gracias!
Jim Clouse

4
Sí, el problema con rootScope es que debe recordar desvincular ese controlador cuando su controlador desaparezca.
lostintranslation

12

Una solución es transmitir un evento 'notAuthorized' y capturarlo en el alcance principal para volver a cambiar la ubicación. Creo que no es la mejor solución, pero funcionó para mí:

myApp.run(['$rootScope', 'LoginService',
    function ($rootScope, LoginService) {
        $rootScope.$on('$routeChangeStart', function (event, next, current) {
            var authorizedRoles = next.data ? next.data.authorizedRoles : null;
            if (LoginService.isAuthenticated()) {
                if (!LoginService.isAuthorized(authorizedRoles)) {
                    $rootScope.$broadcast('notAuthorized');
                }
            }
        });
    }
]);

y en mi controlador principal:

    $scope.$on('notAuthorized', function(){
        $location.path('/forbidden');
    });

Nota: hay una discusión sobre este problema en el sitio angular, aún no resuelto: https://github.com/angular/angular.js/pull/4192

EDITAR:

Para responder al comentario, aquí hay más información sobre los trabajos de LoginService. Contiene 3 funciones:

  1. login () (el nombre es engañoso) realiza una solicitud al servidor para obtener información sobre el usuario (previamente) registrado. Hay otra página de inicio de sesión que simplemente completa el estado del usuario actual en el servidor (utilizando el marco SpringSecurity). Mis servicios web no son verdaderamente apátridas, pero preferí dejar que ese famoso marco manejara mi seguridad.
  2. isAuthenticated () simplemente busque si la sesión del cliente está llena de datos, lo que significa que se ha autenticado antes (*)
  3. isAuthorized () manejó los derechos de acceso (fuera del alcance de este tema).

(*) Mi sesión se completa cuando cambia la ruta. He anulado el método when () para completar la sesión cuando está vacía.

Aquí está el código:

services.factory('LoginService', ['$http', 'Session', '$q',
function($http, Session, $q){
    return {
        login: function () {
            var defer = $q.defer();
            $http({method: 'GET', url: restBaseUrl + '/currentUser'})
                .success(function (data) {
                    defer.resolve(data);
                });
            return defer.promise;
        },
        isAuthenticated: function () {
            return !!Session.userLogin;
        },
        isAuthorized: function (authorizedRoles) {
            if (!angular.isArray(authorizedRoles)) {
                authorizedRoles = [authorizedRoles];
            }

            return (this.isAuthenticated() &&  authorizedRoles.indexOf(Session.userRole) !== -1);
        }
    };
}]);

myApp.service('Session', ['$rootScope',
    this.create = function (userId,userLogin, userRole, userMail, userName, userLastName, userLanguage) {
        //User info
        this.userId = userId;
        this.userLogin = userLogin;
        this.userRole = userRole;
        this.userMail = userMail;
        this.userName = userName;
        this.userLastName = userLastName;
        this.userLanguage = userLanguage;
    };

    this.destroy = function () {
        this.userId = null;
        this.userLogin = null;
        this.userRole = null;
        this.userMail = null;
        this.userName = null;
        this.userLastName = null;
        this.userLanguage = null;
        sessionStorage.clear();
    };

    return this;
}]);

myApp.config(['$routeProvider', 'USER_ROLES', function ($routeProvider, USER_ROLES) {
    $routeProvider.accessWhen = function (path, route) {
        if (route.resolve == null) {
            route.resolve = {
                user: ['LoginService','Session',function (LoginService, Session) {
                    if (!LoginService.isAuthenticated())
                        return LoginService.login().then(function (data) {
                            Session.create(data.id, data.login, data.role, data.email, data.firstName, data.lastName, data.language);
                            return data;
                        });
                }]
            }
        } else {
            for (key in route.resolve) {
                var func = route.resolve[key];
                route.resolve[key] = ['LoginService','Session','$injector',function (LoginService, Session, $injector) {
                    if (!LoginService.isAuthenticated())
                        return LoginService.login().then(function (data) {
                            Session.create(data.id, data.login, data.role, data.email, data.firstName, data.lastName, data.language);
                            return func(Session, $injector);
                        });
                    else
                        return func(Session, $injector);
                }];
            }
        }
    return $routeProvider.when(path, route);
    };

    //use accessWhen instead of when
    $routeProvider.
        accessWhen('/home', {
            templateUrl: 'partials/dashboard.html',
            controller: 'DashboardCtrl',
            data: {authorizedRoles: [USER_ROLES.superAdmin, USER_ROLES.admin, USER_ROLES.system, USER_ROLES.user]},
            resolve: {nextEvents: function (Session, $injector) {
                $http = $injector.get('$http');
                return $http.get(actionBaseUrl + '/devices/nextEvents', {
                    params: {
                        userId: Session.userId, batch: {rows: 5, page: 1}
                    },
                    isArray: true}).then(function success(response) {
                    return response.data;
                });
            }
        }
    })
    ...
    .otherwise({
        redirectTo: '/home'
    });
}]);

¿Puede decir qué retorno LoginService.isAuthenticated()al cargar la primera página? ¿Cómo estás almacenando currentUser? ¿Qué sucede si el usuario actualiza la página (el usuario necesita volver a ingresar las credenciales)?
MyTitle

Agregué más información sobre LoginService en mi respuesta original. El servidor proporciona el usuario actual, y el cambio de ruta maneja cualquier actualización de página, no es necesario que el usuario vuelva a iniciar sesión.
Asterius

4

Para cualquiera que se encuentre con esto es una vieja pregunta, (al menos en angular 1.4) puede hacer esto:

 .run(function($rootScope, authenticationService) {
        $rootScope.$on('$routeChangeStart', function (event, next) {
            if (next.require == undefined) return

            var require = next.require
            var authorized = authenticationService.satisfy(require);

            if (!authorized) {
                $rootScope.error = "Not authorized!"
                event.preventDefault()
            }
        })
      })

6
Me pregunto, ¿le cobran extra por el uso de aparatos ortopédicos o ";"?
cel sharp

6
@MatthiasJansen por supuesto. Y para colmo, las llaves cuentan el doble y el punto y coma el triple.
Ákos Vandra

1

Esta es mi solución y funciona para mí, pero no sé si estoy en el camino correcto porque soy nuevo en las tecnologías web.

var app = angular.module("app", ['ngRoute', 'ngCookies']);
app.run(function($rootScope, $location, $cookieStore){
$rootScope.$on('$routeChangeStart', function(event, route){
    if (route.mustBeLoggedOn && angular.isUndefined($cookieStore.get("user"))) {
        // reload the login route
        jError(
             'You must be logged on to visit this page',
             {
               autoHide : true,
               TimeShown : 3000,
               HorizontalPosition : 'right',
               VerticalPosition : 'top',
               onCompleted : function(){ 
               window.location = '#/signIn';
                 window.setTimeout(function(){

                 }, 3000)
             }
        });
    }
  });
});

app.config(function($routeProvider){
$routeProvider
    .when("/signIn",{
        controller: "SignInController",
        templateUrl: "partials/signIn.html",
        mustBeLoggedOn: false
});

2
¿Cómo puede responder una pregunta si no está seguro de su respuesta?
deW1

Estoy seguro de que funciona. No estoy seguro de si esta es la forma correcta. Si tiene una mejor solución, me gustaría verla.
Alexandrakis alexandros

1

Encontré este relevante

var myApp = angular.module('myApp', []);

myApp.run(function($rootScope) {
    $rootScope.$on("$locationChangeStart", function(event, next, current) { 
        // handle route changes  
$rootScope.error = "Not authorized!"
                event.preventDefault()   
    });
});

mi publicación puede ayudar a alguien en el futuro.


1
var app=angular
    .module('myapp', [])
    .controller('myctrl', function($rootScope) {
        $rootScope.$on("locationChangeStart", function(event, next, current) {
        if (!confirm("location changing to:" + next)) { 
            event.preventDefault();
        }
    })
});

2
Si bien este fragmento de código puede resolver la pregunta, incluir una explicación realmente ayuda a mejorar la calidad de su publicación. Recuerde que está respondiendo a la pregunta para los lectores en el futuro, y es posible que esas personas no conozcan los motivos de su sugerencia de código.
dpr

0

En caso de que necesite evitar que la ruta cambie en el $routeChangeStartevento (es decir, si desea realizar alguna operación basada en la siguiente ruta ), inyecte $routey $routeChangeStartllame internamente:

$route.reload()

1
Tenía esperanzas, pero en Angular 1.2.7 en Chrome, esto parece causar un bucle JS y la página se congela.
Nick Spacek

1
@NickSpacek La condición en la que llama $route.reload()debe ser diferente o, de lo contrario, se está ejecutando el mismo código nuevamente. Este es el equivalente a crear un whilebucle con truela condición.
Kevin Beal

0

Solo para compartir, en mi caso quiero retrasar la resolución de la ruta con $ routeChangeStart. Tengo un SomethingService que debe cargarse antes de que comience la resolución de la ruta (sí, aplicación habladora), por lo tanto, tengo la promesa de esperar. Quizás encontré un truco ... La resolución de la ruta falla si una resolución devuelve un rechazo. Rompí la configuración de resolución y la soluciono más tarde.

    var rejectingResolve = {
        cancel: function ($q){
            // this will cancel $routeChangeStart
            return $q.reject();
        }
    }
    
    $rootScope.$on("$routeChangeStart", function(event, args, otherArgs) {
        var route = args.$$route,
            originalResolve = route.resolve;
    
        if ( ! SomethingService.isLoaded() ){

            SomethingService.load().then(function(){
                // fix previously destroyed route configuration
                route.resolve = originalResolve;
                
                $location.search("ts", new Date().getTime());
                // for redirections
                $location.replace();
            });

            // This doesn't work with $routeChangeStart: 
            // we need the following hack
            event.preventDefault();
            
            // This is an hack! 
            // We destroy route configuration, 
            // we fix it back when SomethingService.isLoaded
            route.resolve = rejectingResolve;
        } 
    });
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.