AngularJs - cancelar evento de alteração de rota

88

Como cancelo o evento de mudança de rota no AngularJs?

Meu código atual é

$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  

}
});

com isso, mesmo se a validação falhar, o Angular puxa o próximo modelo e os dados associados e, em seguida, volta imediatamente para a visualização / rota anterior. Não quero que o angular extraia o próximo modelo e dados se a validação falhar, o ideal é que não haja window.history.back (). Eu até tentei event.preventDefault () mas não adiantou.

R Arun
fonte

Respostas:

184

Em vez de $routeChangeStartusar$locationChangeStart

Aqui está a discussão sobre isso com os caras do angularjs: https://github.com/angular/angular.js/issues/2109

Editar 06/03/2018 Você pode encontrá-lo nos documentos: https://docs.angularjs.org/api/ng/service/$location#event-$locationChangeStart

Exemplo:

$scope.$on('$locationChangeStart', function(event, next, current) {
    if ($scope.form.$invalid) {
       event.preventDefault();
    }
});
Mathew Berg
fonte
8
O problema com isso é que não há como acessar a coleção de parâmetros da rota. Se você está tentando validar parâmetros de rota, esta solução não é boa.
KingOfHypocrites
1
Observe que ao usar $routeChangeStarta nextvariável é apenas uma string e não pode conter nenhum dado (por exemplo, você não pode acessar a authorizedRolesvariável anterior definida )
MyTitle
@KingOfHypocrites, você não pode obter parâmetros de rota, mas pode obter $location.path()e$location.search()
por Adrian
2
Você pode / é aconselhável fazer isso no rootScope se quiser rastrear todas as alterações de rota? Ou existe uma alternativa mais palatável?
máx.
38

Um exemplo de código mais completo, 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); 
      });
    }
  );

Não estou completamente feliz em conectar isso ao meu controlador de raiz (controlador de nível superior). Se houver um padrão melhor, adoraria saber. Eu sou novo no angular :-)

Shyam Habarakada
fonte
Isso funcionou muito bem para mim, embora eu não esteja tentando cancelar minha mudança de rota como o pôster original. Obrigado!
Jim Clouse
4
Sim, o problema com o rootScope é que você deve se lembrar de desvincular esse manipulador quando o controlador for desligado.
perdeu a tradução em
12

Uma solução é transmitir um evento 'notAuthorized' e capturá-lo no escopo principal para alterar novamente o local. Acho que não é a melhor solução, mas funcionou para mim:

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

e no meu controlador principal:

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

Nota: há alguma discussão sobre este problema no site angular, ainda não resolvido: https://github.com/angular/angular.js/pull/4192

EDITAR:

Para responder ao comentário, aqui estão mais informações sobre os trabalhos do LoginService. Ele contém 3 funções:

  1. login () (o nome é enganoso) faz uma solicitação ao servidor para obter informações sobre o usuário (anteriormente) logado. Há outra página de login que apenas preenche o estado do usuário atual no servidor (usando a estrutura SpringSecurity). Meus serviços da Web não são verdadeiramente sem estado, mas preferi deixar essa famosa estrutura cuidar da minha segurança.
  2. isAuthenticated () apenas pesquise se a Sessão do cliente está preenchida com dados, o que significa que foi autenticada antes (*)
  3. isAuthorized () direitos de acesso tratados (fora do escopo para este tópico).

(*) Minha sessão é preenchida quando a rota muda. Eu substituí o método when () para preencher a sessão quando vazia.

Aqui está o 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'
    });
}]);
Asterius
fonte
Você pode dizer o que retorna LoginService.isAuthenticated()no primeiro carregamento da página? Como você está armazenando currentUser? O que acontece se o usuário atualizar a página (o usuário precisa inserir novamente as credenciais)?
MyTitle
Eu adicionei mais informações sobre o LoginService em minha resposta original. O currentUser é fornecido pelo servidor e a mudança de rota trata de qualquer atualização de página, não há necessidade de o usuário se registrar novamente.
Asterius
4

Para qualquer um que topar com esta é uma questão antiga, (pelo menos no angular 1.4) você pode fazer isso:

 .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
fonte
6
Eu me pergunto, você é cobrado a mais pelo uso de chaves ou ";"?
cel sharp
6
@MatthiasJansen, é claro. E ainda por cima, os colchetes contam o dobro e os pontos-e-vírgulas contam o triplo.
Ákos Vandra
1

Esta é a minha solução e funciona para mim, mas não sei se estou no caminho certo porque sou novo nas tecnologias 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
fonte
2
Como você pode responder a uma pergunta se não tiver certeza de sua resposta?
deW1
Tenho certeza de que funciona. Não tenho certeza se esse é o caminho correto. Se você tiver uma solução melhor, gostaria de vê-la.
Alexandrakis alexandros
1

eu achei 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()   
    });
});

minha postagem pode ajudar alguém no futuro.

Monojit Sarkar
fonte
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();
        }
    })
});
Macle Studio
fonte
2
Embora este snippet de código possa resolver a questão, incluir uma explicação realmente ajuda a melhorar a qualidade de sua postagem. Lembre-se de que você está respondendo à pergunta para leitores no futuro e essas pessoas podem não saber os motivos de sua sugestão de código.
dpr
0

Caso você precise fazer parar a mudança de rota no $routeChangeStartevento (ou seja, você deseja realizar alguma operação baseada na próxima rota ), injete $routee $routeChangeStartchame:

$route.reload()
senhor
fonte
1
Eu estava esperançoso, mas no Angular 1.2.7 no Chrome isso parece causar um loop JS e a página congela.
Nick Spacek
1
@NickSpacek A condição na qual você chama $route.reload()precisa ser diferente ou então está executando o mesmo código novamente. Isso é equivalente a criar um whileloop com truea condição.
Kevin Beal,