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é.

R Arun
fuente

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();
    }
});
Mathew Berg
fuente
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 :-)

Shyam Habarakada
fuente
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'
    });
}]);
Asterio
fuente
¿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()
            }
        })
      })
Ákos Vandra
fuente
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
});
Alexandrakis alexandros
fuente
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.

Monojit Sarkar
fuente
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();
        }
    })
});
Estudio Macle
fuente
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()
mrt
fuente
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;
        } 
    });
Plap
fuente