AngularJs - cancel route change event

JavascriptAngularjs

Javascript Problem Overview


How do I cancel route change event in AngularJs?

My current code is

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

with this even if the validation fails Angular pulls the next template and associated data and then immediately switches back to previous view/route. I don't want angular to pull next template & data if validation fails, ideally there should be no window.history.back(). I even tried event.preventDefault() but no use.

Javascript Solutions


Solution 1 - Javascript

Instead of $routeChangeStart use $locationChangeStart

Here's the discussion about it from the angularjs guys: https://github.com/angular/angular.js/issues/2109

Edit 3/6/2018 You can find it in the docs: https://docs.angularjs.org/api/ng/service/$location#event-$locationChangeStart

Example:

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

Solution 2 - Javascript

A more complete code sample, using $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); 
      });
    }
  );

I'm not completely happy with hooking this up in my root controller (top level controller). If there is a better pattern, I'd love to know. I'm new to angular :-)

Solution 3 - Javascript

A solution is to broadcast a 'notAuthorized' event, and catch it in the main scope to re-change the location. I think it is not the best solution, but it worked for me:

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

and in my Main Controller:

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

Note: there is some discussion about this problem on angular site, not yet solved: https://github.com/angular/angular.js/pull/4192

EDIT:

To answer the comment, here is more information about the LoginService works. It contains 3 functions:

  1. login() (name is misleading) do a request to the server to get information about the (previously) logged user. There is another login page which just populate the current user state in the server (using SpringSecurity framework). My Web Services are not truely stateless, but I preferred to let that famous framework handle my security .
  2. isAuthenticated() just search if the client Session is filled with data, which means it has been authenticated before (*)
  3. isAuthorized() handled access rights (out of scope for this topic).

(*) My Session is populated when the route change. I have overridden then when() method to populate the session when empty.

Here is the code :

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

Solution 4 - Javascript

For anyone stumbling upon this is an old question, (at least in angular 1.4) you can do this:

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

Solution 5 - Javascript

This is my solution and it works for me but i don't know if i am on the right way cause i am new to web technologies.

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

Solution 6 - Javascript

i found this one relevant

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

my post may help some one in future.

Solution 7 - Javascript

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

Solution 8 - Javascript

In case you need to do stop the route from changing in the $routeChangeStart event (i.e. you want to perform some operation based on the next route), inject $route and inside $routeChangeStart call:

$route.reload()

Solution 9 - Javascript

Just to share, in my case I want to delay route resolution with $routeChangeStart. I've got a SomethingService that must load before the resolution of the route starts (yes, chatty application) hence I've got a promise to wait for. Maybe I found an hack... The resolution of the route goes in error if a resolve return a rejection. I broken the resolve configuration and I fix it back later.

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionR ArunView Question on Stackoverflow
Solution 1 - JavascriptMathew BergView Answer on Stackoverflow
Solution 2 - JavascriptShyam HabarakadaView Answer on Stackoverflow
Solution 3 - JavascriptAsteriusView Answer on Stackoverflow
Solution 4 - JavascriptÁkos Vandra-MeyerView Answer on Stackoverflow
Solution 5 - JavascriptAlexandrakis alexandrosView Answer on Stackoverflow
Solution 6 - JavascriptMonojit SarkarView Answer on Stackoverflow
Solution 7 - JavascriptMacle StudioView Answer on Stackoverflow
Solution 8 - JavascriptmrtView Answer on Stackoverflow
Solution 9 - JavascriptPlapView Answer on Stackoverflow