I want to prevent some flickering that happens when rails devise timeout occurs, but angular doesn\'t know until the next authorization error from a resource.
What h
var lastTransition = null;
$rootScope.$on('$stateChangeStart',
function(event, toState, toParams, fromState, fromParams, options) {
// state change listener will keep getting fired while waiting for promise so if detect another call to same transition then just return immediately
if(lastTransition === toState.name) {
return;
}
lastTransition = toState.name;
// Don't do transition until after promise resolved
event.preventDefault();
return executeFunctionThatReturnsPromise(fromParams, toParams).then(function(result) {
$state.go(toState,toParams,options);
});
});
I had some issues using a boolean guard for avoiding infinite loop during stateChangeStart so took this approach of just checking if the same transition was attempted again and returning immediately if so since for that case the promise has still not resolved.
I believe you are looking for event.preventDefault()
Note: Use event.preventDefault() to prevent the transition from happening.
$scope.$on('$stateChangeStart',
function(event, toState, toParams, fromState, fromParams){
event.preventDefault();
// transitionTo() promise will be rejected with
// a 'transition prevented' error
})
Although I would probably use resolve
in state config as @charlietfl suggested
EDIT:
so I had a chance to use preventDefault() in state change event, and here is what I did:
.run(function($rootScope,$state,$timeout) {
$rootScope.$on('$stateChangeStart',
function(event, toState, toParams, fromState, fromParams){
// check if user is set
if(!$rootScope.u_id && toState.name !== 'signin'){
event.preventDefault();
// if not delayed you will get race conditions as $apply is in progress
$timeout(function(){
event.currentScope.$apply(function() {
$state.go("signin")
});
},300)
} else {
// do smth else
}
}
)
}
EDIT
Newer documentation includes an example of how one should user sync()
to continue after preventDefault
was invoked, but exaple provided there uses $locationChangeSuccess
event which for me and commenters does not work, instead use $stateChangeStart
as in the example below, taken from docs with an updated event:
angular.module('app', ['ui.router'])
.run(function($rootScope, $urlRouter) {
$rootScope.$on('$stateChangeStart', function(evt) {
// Halt state change from even starting
evt.preventDefault();
// Perform custom logic
var meetsRequirement = ...
// Continue with the update and state transition if logic allows
if (meetsRequirement) $urlRouter.sync();
});
});
I really like the suggested solution by TheRyBerg, since you can do all in one place and without too much weird tricks. I have found that there is a way to improve it even further, so that you don't need the stateChangeBypass in the rootscope. The main idea is that you want to have something initialized in your code before your application can "run". Then if you just remember if it's initialized or not you can do it this way:
rootScope.$on("$stateChangeStart", function (event, toState, toParams, fromState) {
if (dataService.isInitialized()) {
proceedAsUsual(); // Do the required checks and redirects here based on the data that you can expect ready from the dataService
}
else {
event.preventDefault();
dataService.intialize().success(function () {
$state.go(toState, toParams);
});
}
});
Then you can just remember that your data is already initialized in the service the way you like, e.g.:
function dataService() {
var initialized = false;
return {
initialize: initialize,
isInitialized: isInitialized
}
function intialize() {
return $http.get(...)
.success(function(response) {
initialized=true;
});
}
function isInitialized() {
return initialized;
}
};
as $urlRouter.sync() doesn't work with stateChangeStart, here's an alternative:
var bypass;
$rootScope.$on('$stateChangeStart', function(event,toState,toParams) {
if (bypass) return;
event.preventDefault(); // Halt state change from even starting
var meetsRequirement = ... // Perform custom logic
if (meetsRequirement) { // Continue with the update and state transition if logic allows
bypass = true; // bypass next call
$state.go(toState, toParams); // Continue with the initial state change
}
});