Why does AngularJS with ui-router keep firing the $stateChangeStart event?
angular-ui-router, angularjs
Solution
Fakeout. This is an interaction issue between `$urlRouterProvider` and `$stateProvider`. I shouldn't be using `$urlRouterProvider` for my `otherwise`. I should be using something like:
$stateProvider.state("otherwise", {
url: "*path",
template: "Invalid Location",
controller: [
'$timeout','$state',
function($timeout, $state ) {
$timeout(function() {
$state.go('/foo')
},2000)
}]
});
Or even a transparent'ish redirect:
$stateProvider.state("otherwise", {
url: "*path",
template: "",
controller: [
'$state',
function($state) {
$state.go('/foo')
}]
});
Altogether now:
<!doctype html>
<head>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.15/angular.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.10/angular-ui-router.min.js"></script>
</head>
<body ng-app="clientApp">
<div ui-view="" ></div>
<script>
var app = angular.module('clientApp', ['ui.router'])
var myRouteProvider = [
'$stateProvider',
function($stateProvider) {
$stateProvider.state('/foo', {
url: '/foo',
template: '<div>In Foo now</div>',
reloadOnSearch: false
})
$stateProvider.state("otherwise", {
url: "*path",
template: "",
controller: [
'$state',
function($state) {
$state.go('/foo')
}]
});
}]
app.config(myRouteProvider)
var authenticated = false
app.run([
'$rootScope', '$log','$state','$timeout',
function ($rootScope, $log, $state, $timeout) {
$rootScope.$on('$stateChangeStart', function (event, next, toParams) {
if (!authenticated) {
event.preventDefault()
//following $timeout is emulating a backend $http.get('/auth/') request
$timeout(function() {
authenticated = true
$state.go(next,toParams)
},1000)
}
})
}
])
</script>
</body>
</html>
Problem
I'm trying to block all ui-router state changes until I've authenticated the user: ``` $rootScope.$on('$stateChangeStart', function (event, next, toParams) { if (!authenticated) { event.preventDefault() //following $timeout is emulating a backend $http.get('/auth/') request $timeout(function() { authenticated = true $state.go(next,toParams) },1000) } }) ``` I reject all state changes until the user has been authenticated, but if I go to an invalid URL that uses the `otherwise()` configuration, I get an infinite loop with a message: ``` Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting! Watchers fired in the last 5 iterations: [["fn: $locationWatch; newVal: 7; oldVal: 6"],["fn: $locationWatch; newVal: 8; oldVal: 7"],["fn: $locationWatch; newVal: 9; oldVal: 8"],["fn: $locationWatch; newVal: 10; oldVal: 9"],["fn: $locationWatch; newVal: 11; oldVal: 10"]] ``` Below is my SSCCE. Serve it up with `python -m SimpleHTTPServer 7070` and go to `localhost:7070/test.html#/bar` to see it explode in your face. Whereas directly navigating to the only valid angularjs location does not blow up `localhost:7070/test.html#/foo`: ``` <!doctype html> <head> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.js"></script> <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.15/angular.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.10/angular-ui-router.min.js"></script> </head> <body ng-app="clientApp"> <div ui-view="" ></div> <script> var app = angular.module('clientApp', ['ui.router']) var myRouteProvider = [ '$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) { $urlRouterProvider.otherwise('/foo'); $stateProvider.state('/foo', { url: '/foo', template: '<div>In Foo now</div>', reloadOnSearch: false }) }] app.config(myRouteProvider) var authenticated = false app.run([ '$rootScope', '$log','$state','$timeout', function ($rootScope, $log, $state, $timeout) { $rootScope.$on('$stateChangeStart', function (event, next, toParams) { if (!authenticated) { event.preventDefault() //following $timeout is emulating a backend $http.get('/auth/') request $timeout(function() { authenticated = true $state.go(next,toParams) },1000) } }) } ]) </script> </body> </html> ``` Is there an alternative method I should use to accomplish this authentication blocking? I do realize this authentication blocking is client side only. I'm not showing the server side of things in this example.