AngularJs - membatalkan acara perubahan rute


88

Bagaimana cara membatalkan acara perubahan rute di AngularJs?

Kode saya saat ini adalah

$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  

}
});

dengan ini bahkan jika validasi gagal Angular menarik template berikutnya dan data terkait dan kemudian segera beralih kembali ke tampilan / rute sebelumnya. Saya tidak ingin angular menarik template & data berikutnya jika validasi gagal, idealnya tidak ada window.history.back (). Saya bahkan mencoba event.preventDefault () tetapi tidak ada gunanya.

Jawaban:


184

Alih-alih $routeChangeStartdigunakan$locationChangeStart

Berikut pembahasannya dari angularjs guys: https://github.com/angular/angular.js/issues/2109

Edit 3/6/2018 Anda dapat menemukannya di dokumen: https://docs.angularjs.org/api/ng/service/$location#event-$locationChangeStart

Contoh:

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

8
Masalahnya adalah tidak ada cara untuk mengakses koleksi parameter rute. Jika Anda mencoba memvalidasi parameter rute, solusi ini tidak baik.
KingOfHypocrites

1
Harap dicatat, bila menggunakan $routeChangeStartyang nextvariabel hanya string dan tidak dapat berisi data (misalnya Anda tidak dapat akses ke didefinisikan sebelumnya authorizedRolesvariabel)
MyTitle

@KingOfHypocrites Anda tidak bisa mendapatkan parameter rute, tetapi Anda bisa mendapatkan $location.path()dan$location.search()
Desain oleh Adrian

2
Dapatkah Anda / apakah Anda disarankan untuk melakukan ini pada rootScope jika Anda ingin melacak semua perubahan rute? Atau apakah ada alternatif yang lebih enak?
maks.

38

Contoh kode yang lebih lengkap, menggunakan $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); 
      });
    }
  );

Saya tidak sepenuhnya senang dengan menghubungkan ini di pengontrol root saya (pengontrol tingkat atas). Jika ada pola yang lebih baik, saya ingin tahu. Saya baru mengenal angular :-)


Ini bekerja dengan baik untuk saya, meskipun saya tidak mencoba membatalkan perubahan rute saya seperti poster orig. Terima kasih!
Jim Clouse

4
Ya, masalah dengan rootScope adalah Anda harus ingat untuk melepaskan penangan itu saat pengontrol Anda hilang.
lostintranslation

12

Solusinya adalah menyiarkan acara 'notAuthorized', dan menangkapnya di ruang lingkup utama untuk mengubah ulang lokasi. Saya pikir ini bukan solusi terbaik, tetapi berhasil untuk saya:

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

dan di Pengontrol Utama saya:

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

Catatan: Ada beberapa diskusi tentang masalah ini di situs sudut, belum terpecahkan: https://github.com/angular/angular.js/pull/4192

EDIT:

Untuk menjawab komentar tersebut, berikut adalah informasi lebih lanjut tentang cara kerja LoginService. Ini berisi 3 fungsi:

  1. login () (nama menyesatkan) melakukan permintaan ke server untuk mendapatkan informasi tentang pengguna yang (sebelumnya) masuk. Ada halaman login lain yang hanya mengisi status pengguna saat ini di server (menggunakan framework SpringSecurity). Layanan Web saya tidak benar-benar tanpa kewarganegaraan, tetapi saya lebih suka membiarkan kerangka kerja terkenal itu menangani keamanan saya.
  2. isAuthenticated () hanya mencari jika Sesi klien diisi dengan data, yang berarti telah diautentikasi sebelumnya (*)
  3. isAuthorized () menangani hak akses (di luar cakupan topik ini).

(*) Sesi Saya diisi saat rute berubah. Saya telah mengganti metode then when () untuk mengisi sesi saat kosong.

Ini kodenya:

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

Bisakah Anda mengatakan apa hasil LoginService.isAuthenticated()saat memuat halaman pertama? Bagaimana Anda menyimpan currentUser? Apa yang terjadi jika pengguna menyegarkan halaman (pengguna perlu memasukkan kembali kredensial)?
MyTitle

Saya menambahkan lebih banyak informasi tentang LoginService di jawaban asli saya. CurrentUser disediakan oleh server, dan perubahan rute menangani setiap penyegaran halaman, pengguna tidak perlu masuk lagi.
Asterius

4

Bagi siapa pun yang tersandung pada ini adalah pertanyaan lama, (setidaknya dalam sudut 1.4) Anda dapat melakukan ini:

 .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
Saya ingin tahu, apakah Anda dikenai biaya tambahan untuk penggunaan kawat gigi atau ";"?
sel tajam

6
@MatthiasJansen tentu saja. Dan untuk melengkapi semua ini, tanda kurung dihitung dua kali, dan titik koma dihitung tiga kali lipat.
Ákos Vandra

1

Ini adalah solusi saya dan berfungsi untuk saya tetapi saya tidak tahu apakah saya berada di jalan yang benar karena saya baru mengenal teknologi 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
Bagaimana Anda bisa menjawab pertanyaan jika Anda tidak yakin dengan jawaban Anda?
deW1

Saya yakin itu berhasil. Saya tidak yakin apakah ini cara yang tepat. Jika Anda memiliki solusi yang lebih baik, saya ingin melihatnya.
Alexandrakis alexandros

1

saya menemukan yang satu ini relevan

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

posting saya mungkin membantu seseorang di masa depan.


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
Meskipun cuplikan kode ini dapat menyelesaikan pertanyaan, menyertakan penjelasan sangat membantu meningkatkan kualitas posting Anda. Ingatlah bahwa Anda menjawab pertanyaan untuk pembaca di masa mendatang, dan orang-orang itu mungkin tidak tahu alasan saran kode Anda.
dpr

0

Jika Anda perlu menghentikan rute agar tidak berubah dalam $routeChangeStartacara (yaitu Anda ingin melakukan beberapa operasi berdasarkan rute berikutnya ), injeksi $routedan $routeChangeStartpanggilan dalam :

$route.reload()

1
Saya berharap, tetapi di Angular 1.2.7 di Chrome ini tampaknya menyebabkan loop JS dan halaman macet.
Nick Spacek

1
@NickSpacek Kondisi di mana Anda memanggil $route.reload()harus berbeda atau menjalankan kode yang sama lagi. Ini sama dengan membuat whileloop dengan truesebagai kondisi.
Kevin Beal

0

Sekadar berbagi, dalam kasus saya, saya ingin menunda resolusi rute dengan $ routeChangeStart. Saya punya SomethingService yang harus dimuat sebelum resolusi rute dimulai (ya, aplikasi cerewet) maka saya punya janji untuk menunggu. Mungkin saya menemukan peretasan ... Resolusi rute mengalami kesalahan jika penyelesaian mengembalikan penolakan. Saya merusak konfigurasi penyelesaian dan saya memperbaikinya kembali nanti.

    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;
        } 
    });
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.