Почему AngularJS с ui-router продолжает запускать событие $stateChangeStart?
Я пытаюсь заблокировать все изменения состояния ui-router, пока я не аутентифицировал пользователя:
$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)
}
})
Я отклоняю все изменения состояния до тех пор, пока пользователь не будет аутентифицирован, но если я перейду на неверный URL-адрес, который использует конфигурацию otherwise()
, я получаю бесконечный цикл с сообщением:
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"]]
Ниже приведена SSCCE. Подайте его с помощью python -m SimpleHTTPServer 7070
и перейдите к localhost:7070/test.html#/bar
, чтобы увидеть, как он взорвался в вашем лице. Если прямое перемещение к единственному действительному местоположению углов не взорвется 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>
Есть ли альтернативный метод, который я должен использовать для выполнения этой блокировки аутентификации? Я понимаю, что эта блокировка аутентификации является только клиентской. Я не показываю серверную часть вещей в этом примере.
Ответы
Ответ 1
Fakeout. Это проблема взаимодействия между $urlRouterProvider
и $stateProvider
. Я не должен использовать $urlRouterProvider
для моего otherwise
. Я должен использовать что-то вроде:
$stateProvider.state("otherwise", {
url: "*path",
template: "Invalid Location",
controller: [
'$timeout','$state',
function($timeout, $state ) {
$timeout(function() {
$state.go('/foo')
},2000)
}]
});
Или даже прозрачное перенаправление:
$stateProvider.state("otherwise", {
url: "*path",
template: "",
controller: [
'$state',
function($state) {
$state.go('/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',
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>
Ответ 2
Похоже, что это ошибка с ui-router, когда вы используете комбинацию $urlRouterProvider.otherwise("/foo) с $stateChangeStart.
Проблема - https://github.com/angular-ui/ui-router/issues/600
Фрэнк Уоллис обеспечивает хорошее обходное решение, использует более длинную форму иначе используемого метода, который принимает функцию в качестве аргумента:
$urlRouterProvider.otherwise( function($injector, $location) {
var $state = $injector.get("$state");
$state.go("app.home");
});
Хорошая работа Фрэнка!
Ответ 3
У меня также была эта проблема. Ниже приведен код для обхода, который вдохновлен angular-permission.
Основная идея состоит в том, чтобы добавить флаг ($$finishAuthorize
) в состояние вручную и разбить бесконечный цикл на этот флаг. Еще один момент, который нам нужно знать, - это опция {notify: false}
$state.go
и трансляция события "$stateChangeSuccess"
вручную.
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
if (toState.$$finishAuthorize) {
return;
}
if (!authenticated) {
event.preventDefault();
toState = angular.extend({'$$finishAuthorize': true}, toState);
// following $timeout is emulating a backend $http.get('/auth/') request
$timeout(function() {
authenticated = true;
$state.go(toState.name, toParams, {notify: false}).then(function() {
$rootScope.$broadcast('$stateChangeSuccess', toState, toParams, fromState, fromParams);
});
},1000)
}
);
Ответ 4
У меня также была эта проблема. Оказывается, это был код, который они предложили сделать трейлинг-косой чертой необязательно https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions#how-to-make-a-trailing-slash-optional-for-all-routes
$urlRouterProvider.rule(function ($injector, $location) {
var path = $location.url();
console.log(path);
// check to see if the path already has a slash where it should be
if (path[path.length - 1] === '/' || path.indexOf('/?') > -1) {
return;
}
if (path.indexOf('?') > -1) {
return path.replace('?', '/?');
}
return path + '/';
});
изменил это на
$urlRouterProvider.rule(function ($injector, $location) {
var path = $location.url();
// check to see if the path already has a slash where it should be
if (path[path.length - 1] === '/' || path.indexOf('/?') > -1) {
return;
}
if (path.indexOf('?') > -1) {
$location.replace().path(path.replace('?', '/?'));
}
$location.replace().path(path + '/');
});
не возвращать новый путь и просто заменить его не вызывает StateChangeStart
Ответ 5
Попробуйте изменить свой блок выполнения на это:
app.run([
'$rootScope', '$log','$state','$interval',
function ($rootScope, $log, $state, $interval) {
var authenticated = false;
$rootScope.$on('$stateChangeStart', function (event, next, toParams) {
if (!authenticated) {
event.preventDefault()
//following $timeout is emulating a backend $http.get('/auth/') request
}
})
var intervalCanceller = $interval(function() {
//backend call
if(call succeeds & user authenticated) {
authenticated = true;
$interval.cancel(intervalCanceller);
$state.go(next, toParams);
}
}, 3000);
}
])
Ответ 6
Я пробовал вышеупомянутые решения с разной степенью успеха (создаю приложение ионной кордовой). В какой-то момент мне не удалось получить бесконечные циклы, и состояние изменилось, но у меня остался пустой вид. Я добавил { reload:true }
и, похоже, поможет. Я пытался с { notify:false }
и { notify: true }
, и это не помогло.
В итоге я воспользовался большей частью ответа: fooobar.com/info/451523/...
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
// Do not redirect if going to an error page
if (toState.name === 'app.error') {
return;
}
// Do not redirect if going to the login page
if (toState.name === 'app.login') {
return;
}
// Do not redirect if there is a token present in localstorage
var authData = localstorage.getItem('auth');
if (authData.token) {
return;
}
// We do not have a token, are not going to the login or error pages, time to redirect!
event.preventDefault();
console.debug('No auth credentials in localstorage, redirecting to login page');
$state.go('engineerApp.home', {}, {reload: true}); // Empty object is params
});