AngularJs - anuluj zdarzenie zmiany trasy

88

Jak anulować zdarzenie zmiany trasy w AngularJs?

Mój obecny kod to

$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  

}
});

z tym, nawet jeśli walidacja się nie powiedzie, Angular pobiera następny szablon i powiązane dane, a następnie natychmiast przełącza się z powrotem do poprzedniego widoku / trasy. Nie chcę, aby angular ściągał następny szablon i dane, jeśli walidacja się nie powiedzie, najlepiej byłoby, gdyby nie było okna.history.back (). Próbowałem nawet event.preventDefault (), ale bez sensu.

R Arun
źródło

Odpowiedzi:

184

Zamiast $routeChangeStartużywać$locationChangeStart

Oto dyskusja na ten temat od facetów z angularjs: https://github.com/angular/angular.js/issues/2109

Edycja 06.03.2018 Można go znaleźć w dokumentacji: https://docs.angularjs.org/api/ng/service/$location#event-$locationChangeStart

Przykład:

$scope.$on('$locationChangeStart', function(event, next, current) {
    if ($scope.form.$invalid) {
       event.preventDefault();
    }
});
Mathew Berg
źródło
8
Problem polega na tym, że nie ma możliwości uzyskania dostępu do kolekcji parametrów trasy. Jeśli próbujesz zweryfikować parametry trasy, to rozwiązanie nie jest dobre.
KingOfHypocrites
1
Należy pamiętać, że podczas korzystania $routeChangeStartz nextzmienną jest po prostu ciągiem i nie może zawierać żadnych danych (na przykład nie można dostęp do zdefiniowanej wcześniej authorizedRoleszmiennej)
MyTitle
@KingOfHypocrites nie można uzyskać parametry trasy, ale można dostać $location.path()i$location.search()
Design by Adrian
2
Czy możesz / czy jest to wskazane, aby zrobić to w rootScope, jeśli chcesz śledzić wszystkie zmiany trasy? Czy jest bardziej smaczna alternatywa?
maxm
38

Bardziej kompletny przykład kodu przy użyciu $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); 
      });
    }
  );

Nie jestem do końca zadowolony z podłączenia tego do mojego kontrolera głównego (kontrolera najwyższego poziomu). Jeśli istnieje lepszy wzór, chciałbym wiedzieć. Jestem nowy w Angular :-)

Shyam Habarakada
źródło
To zadziałało świetnie, chociaż nie próbuję anulować zmiany trasy, jak na plakacie z początku. Dzięki!
Jim Clouse
4
Tak, problem z rootScope polega na tym, że musisz pamiętać o rozwiązaniu tego programu obsługi, gdy twój kontroler zniknie.
Lostintranslation
12

Rozwiązaniem jest transmitowanie zdarzenia „notAuthorized” i przechwytywanie go w głównym zakresie w celu ponownej zmiany lokalizacji. Myślę, że to nie jest najlepsze rozwiązanie, ale u mnie zadziałało:

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

aw moim głównym kontrolerze:

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

Uwaga: na stronie kątowej jest dyskusja na temat tego problemu, jeszcze nie rozwiązana: https://github.com/angular/angular.js/pull/4192

EDYTOWAĆ:

Aby odpowiedzieć na komentarz, poniżej znajduje się więcej informacji o działaniu usługi LoginService. Zawiera 3 funkcje:

  1. login () (nazwa wprowadza w błąd) wysyła żądanie do serwera w celu uzyskania informacji o (wcześniej) zalogowanym użytkowniku. Istnieje inna strona logowania, która po prostu zapełnia bieżący stan użytkownika na serwerze (przy użyciu struktury SpringSecurity). Moje usługi sieciowe nie są tak naprawdę bezpaństwowe, ale wolałem pozwolić, aby ten słynny framework zajął się moim bezpieczeństwem.
  2. isAuthenticated () po prostu wyszukuje, czy sesja klienta jest wypełniona danymi, co oznacza, że ​​została uwierzytelniona przed (*)
  3. isAuthorized () obsłużył prawa dostępu (poza zakresem tego tematu).

(*) Moja sesja jest zapełniana po zmianie trasy. Zastąpiłem metodę then when (), aby zapełniać sesję, gdy jest pusta.

Oto kod:

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
źródło
Czy możesz powiedzieć, jaki zwrot LoginService.isAuthenticated()przy ładowaniu pierwszej strony? Jak przechowujesz currentUser? Co się stanie, jeśli użytkownik odświeży stronę (użytkownik będzie musiał ponownie wprowadzić poświadczenia)?
MyTitle
Dodałem więcej informacji o usłudze LoginService w mojej oryginalnej odpowiedzi. Bieżący użytkownik jest dostarczany przez serwer, a zmiana trasy obsługuje każde odświeżenie strony, nie ma potrzeby ponownego logowania użytkownika.
Asterius
4

Dla każdego, kto się na to natknie, jest to stare pytanie (przynajmniej w kątowym 1.4), możesz zrobić to:

 .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
źródło
6
Zastanawiam się, czy za używanie szelek lub „;” naliczana jest dodatkowa opłata?
cel ostry
6
@MatthiasJansen oczywiście. Na domiar złego nawiasy klamrowe liczą się podwójnie, a średniki trzykrotnie.
Ákos Vandra
1

To jest moje rozwiązanie i działa dla mnie, ale nie wiem, czy jestem na dobrej drodze, ponieważ jestem nowy w technologiach internetowych.

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
źródło
2
Jak możesz odpowiedzieć na pytanie, jeśli nie jesteś pewien swojej odpowiedzi?
deW1
Jestem pewien, że to działa. Nie jestem pewien, czy to właściwy sposób. Jeśli masz lepsze rozwiązanie, chciałbym je zobaczyć.
Alexandrakis alexandros
1

znalazłem ten odpowiedni

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

mój post może pomóc komuś w przyszłości.

Monojit Sarkar
źródło
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
źródło
2
Chociaż ten fragment kodu może rozwiązać problem, dołączenie wyjaśnienia naprawdę pomaga poprawić jakość Twojego posta. Pamiętaj, że odpowiadasz na pytanie do czytelników w przyszłości, a osoby te mogą nie znać powodów, dla których zaproponowałeś kod.
dpr
0

W przypadku, gdy musisz zatrzymać zmianę trasy w $routeChangeStartprzypadku zdarzenia (tj. Chcesz wykonać jakąś operację w oparciu o następną trasę ), wstrzyknij $routei $routeChangeStartwywołanie wewnętrzne :

$route.reload()
mrt
źródło
1
Miałem nadzieję, ale w Angular 1.2.7 na Chrome wydaje się, że powoduje to pętlę JS i strona zawiesza się.
Nick Spacek
1
@NickSpacek Warunek, w którym wywołujesz, $route.reload()musi być inny, w przeciwnym razie ponownie uruchamia ten sam kod. Jest to odpowiednik tworzenia whilepętli z truewarunkiem.
Kevin Beal
0

Tylko po to, aby udostępnić, w moim przypadku chcę opóźnić rozpoznanie trasy za pomocą $ routeChangeStart. Mam usługę SomethingService, która musi zostać załadowana przed rozpoczęciem rozwiązywania trasy (tak, rozmowa aplikacja), więc mam obietnicę, na którą muszę poczekać. Może znalazłem hack ... Rozwiązanie trasy jest błędne, jeśli rozwiązanie zwraca odrzucenie. Zepsułem konfigurację rozwiązania i naprawiam to później.

    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
źródło