Angular 使用 ui-router 的无限摘要循环
Angular infinite digest loop with ui-router
我最初试图解决的问题是在用户未登录时将用户重定向到登录页面,反之亦然。
我用下面的代码做了这个
.run(function($rootScope, $http, AppService, $state) {
$rootScope.$on('application:refreshtoken', function(rootScope, token) {
if(token) {
$http.defaults.headers.common['X-Auth-Token'] = token;
AppService.setAuthToken(token);
AppService.resetLoginTimeout();
}
});
$rootScope.$on('$stateChangeSuccess', function() {
$http.get('/api/heartbeat');
});
// This is the really pertinent bit...
$rootScope.$on('$stateChangeStart', function(e, toState) {
if(toState.name === 'login') {
if(AppService.getIsLoggedIn()) {
e.preventDefault();
$state.go(AppService.getRedirectPage());
}
} else {
if(!AppService.getIsLoggedIn()) {
e.preventDefault();
$state.go('login');
}
}
});
});
应用服务
.factory('AppService', ['$rootScope', 'locker', '$http', '$state',
function ($rootScope, locker, $http, $state) {
var _isLoggedIn = locker.get('loggedIn', false),
_authToken = locker.get('authtoken', null),
_roles = locker.get('roles', null),
_permissions = locker.get('permissions', null),
_user = locker.get('user', null),
_userid = locker.get('userid', null),
_userprefs = locker.get('userprefs', null),
_timedout,
_timeoutId,
service = {};
if (_authToken) {
$http.defaults.headers.common['X-Auth-Token'] = _authToken;
}
service.setIsLoggedIn = function (isLoggedIn) {
_isLoggedIn = isLoggedIn;
this.doLogin();
broadcastLogin();
};
service.doLogin = function () {
if (_isLoggedIn) {
locker.put({
loggedIn: _isLoggedIn,
authtoken: _authToken,
roles: _roles,
permissions: _permissions,
user: _user,
userprefs: _userprefs
});
}
};
service.doLogout = function (cb) {
_isLoggedIn = false;
_authToken = null;
_roles = null;
_permissions = null;
_user = null;
_userid = null;
_userprefs = null;
delete $http.defaults.headers.common['X-Auth-Token'];
locker.clean();
cb();
};
service.getIsLoggedIn = function () {
return _isLoggedIn;
};
service.setAuthToken = function (authToken) {
_authToken = authToken;
locker.put({
authtoken: _authToken
});
};
service.getAuthToken = function () {
return _authToken;
};
service.setUserid = function (userid) {
locker.put('userid', userid);
_userid = userid;
};
service.getUserid = function () {
return _userid;
};
service.setUser = function (user) {
_user = user;
};
service.getUser = function () {
return _user;
};
service.setRoles = function (roles) {
_roles = roles;
};
service.getRoles = function () {
return _roles;
};
service.setPermissions = function (permissions) {
_permissions = permissions;
};
service.getPermissions = function () {
return _permissions;
};
service.setUserPreferences = function (prefs) {
_userprefs = prefs;
};
service.getUserPreferences = function () {
return _userprefs;
};
service.resetLoginTimeout = function () {
if (_timeoutId) {
clearTimeout(_timeoutId);
}
_timeoutId = setTimeout(function () {
$rootScope.$broadcast('application:logintimeoutwarn');
}, 1000 * 60 * 4);
};
service.setTimedOut = function (timedout) {
_timedout = timedout;
};
service.getTimedOut = function () {
return _timedout;
};
service.extendSession = function () {
$http.get('/api/heartbeat');
};
service.goDefaultUserPage = function () {
var success = false;
if (_userprefs.landingPage) {
$state.go(_userprefs.landingPage);
success = true;
} else {
var permissionRoutes = {
'regimens': 'regimens.do',
'pathways': 'pathways',
'manage.users': 'manageusers.do',
'manage.practices': 'managepractices.do',
'patients': 'patients'
};
_.some(_permissions, function (el) {
var state = $state.get(permissionRoutes[el]);
if (!state.abstract) {
$state.go(state.name);
success = true;
return true;
}
});
}
return success;
};
service.getRedirectPage = function () {
var page = false;
if (_userprefs.landingPage) {
page = _userprefs.landingPage;
} else {
var permissionRoutes = {
'regimens': 'regimens.do',
'pathways': 'pathways',
'manage.users': 'manageusers.do',
'manage.practices': 'managepractices.do',
'patients': 'patients'
};
_.some(_permissions, function (el) {
var state = $state.get(permissionRoutes[el]);
if (!state.abstract) {
page = state.name;
return true;
}
});
}
return page;
};
function broadcastLogin() {
$rootScope.$broadcast('application:loggedinstatus');
}
broadcastLogin();
return service;
}
])
在我采取一组非常具体的操作之前,这段代码工作得很好:
- 登录
- 关闭打开的选项卡或window
- 打开新标签并转到应用程序
因为我仍然登录到应用程序,所以我有一个用户对象和一个有效令牌,但我得到 error:infdig Infinite $digest Loop
。它最终解决并进入正确的状态,但需要一段时间并且路径闪烁(如果需要,我可以 post 视频)。
我尝试在 $rootScope.$on('$stateChangeSuccess')
回调中使用 $location.path
而不是 $state.go
,但问题仍然存在。
这并没有真正影响应用程序的运行,但很烦人。我也真的不想将我的储物柜存储更改为会话存储,因为我希望用户在关闭选项卡并重新打开时保持登录状态。
这通常发生在应用程序卡在通过 resolve 子句的路由拒绝和上一个路由上的自动重定向之间时,登录页面将重定向到某个页面,比如 auth,而 auth 页面需要一些条件才能让你进入,如果它失败或者它会重定向回其他页面,因此循环,确保你的故事是直截了当的,如果需要使用中间状态来清除所有偏好并采用默认路径
我想说的是,这个问题隐藏在 if
语句中 $rootScope.$on('$stateChangeStart'...
检查这个:
- Ui-Router $state.go inside $on('$stateChangeStart') is cauzing an infinite loop
一般建议:
let's redirect ($state.go()) only if needed - else get out of the event listener
$rootScope.$on('$stateChangeStart' ...
if (toState.name === 'login' ){
// going to login ... do not solve it at all
return;
}
第二次检查应该是:用户是否已通过身份验证(并且不会登录)?
if(AppService.getIsLoggedIn()) {
// do not redirect, let him go... he is AUTHENTICATED
return;
}
现在我们有状态,不是登录,用户未认证,我们可以清楚地调用:
// this is a must - stop current flow
e.preventDefault();
$state.go('login'); // go to login
一切都会像我们预期的那样工作
非常详细的解释和working example could be also found here...
我最初试图解决的问题是在用户未登录时将用户重定向到登录页面,反之亦然。
我用下面的代码做了这个
.run(function($rootScope, $http, AppService, $state) {
$rootScope.$on('application:refreshtoken', function(rootScope, token) {
if(token) {
$http.defaults.headers.common['X-Auth-Token'] = token;
AppService.setAuthToken(token);
AppService.resetLoginTimeout();
}
});
$rootScope.$on('$stateChangeSuccess', function() {
$http.get('/api/heartbeat');
});
// This is the really pertinent bit...
$rootScope.$on('$stateChangeStart', function(e, toState) {
if(toState.name === 'login') {
if(AppService.getIsLoggedIn()) {
e.preventDefault();
$state.go(AppService.getRedirectPage());
}
} else {
if(!AppService.getIsLoggedIn()) {
e.preventDefault();
$state.go('login');
}
}
});
});
应用服务
.factory('AppService', ['$rootScope', 'locker', '$http', '$state',
function ($rootScope, locker, $http, $state) {
var _isLoggedIn = locker.get('loggedIn', false),
_authToken = locker.get('authtoken', null),
_roles = locker.get('roles', null),
_permissions = locker.get('permissions', null),
_user = locker.get('user', null),
_userid = locker.get('userid', null),
_userprefs = locker.get('userprefs', null),
_timedout,
_timeoutId,
service = {};
if (_authToken) {
$http.defaults.headers.common['X-Auth-Token'] = _authToken;
}
service.setIsLoggedIn = function (isLoggedIn) {
_isLoggedIn = isLoggedIn;
this.doLogin();
broadcastLogin();
};
service.doLogin = function () {
if (_isLoggedIn) {
locker.put({
loggedIn: _isLoggedIn,
authtoken: _authToken,
roles: _roles,
permissions: _permissions,
user: _user,
userprefs: _userprefs
});
}
};
service.doLogout = function (cb) {
_isLoggedIn = false;
_authToken = null;
_roles = null;
_permissions = null;
_user = null;
_userid = null;
_userprefs = null;
delete $http.defaults.headers.common['X-Auth-Token'];
locker.clean();
cb();
};
service.getIsLoggedIn = function () {
return _isLoggedIn;
};
service.setAuthToken = function (authToken) {
_authToken = authToken;
locker.put({
authtoken: _authToken
});
};
service.getAuthToken = function () {
return _authToken;
};
service.setUserid = function (userid) {
locker.put('userid', userid);
_userid = userid;
};
service.getUserid = function () {
return _userid;
};
service.setUser = function (user) {
_user = user;
};
service.getUser = function () {
return _user;
};
service.setRoles = function (roles) {
_roles = roles;
};
service.getRoles = function () {
return _roles;
};
service.setPermissions = function (permissions) {
_permissions = permissions;
};
service.getPermissions = function () {
return _permissions;
};
service.setUserPreferences = function (prefs) {
_userprefs = prefs;
};
service.getUserPreferences = function () {
return _userprefs;
};
service.resetLoginTimeout = function () {
if (_timeoutId) {
clearTimeout(_timeoutId);
}
_timeoutId = setTimeout(function () {
$rootScope.$broadcast('application:logintimeoutwarn');
}, 1000 * 60 * 4);
};
service.setTimedOut = function (timedout) {
_timedout = timedout;
};
service.getTimedOut = function () {
return _timedout;
};
service.extendSession = function () {
$http.get('/api/heartbeat');
};
service.goDefaultUserPage = function () {
var success = false;
if (_userprefs.landingPage) {
$state.go(_userprefs.landingPage);
success = true;
} else {
var permissionRoutes = {
'regimens': 'regimens.do',
'pathways': 'pathways',
'manage.users': 'manageusers.do',
'manage.practices': 'managepractices.do',
'patients': 'patients'
};
_.some(_permissions, function (el) {
var state = $state.get(permissionRoutes[el]);
if (!state.abstract) {
$state.go(state.name);
success = true;
return true;
}
});
}
return success;
};
service.getRedirectPage = function () {
var page = false;
if (_userprefs.landingPage) {
page = _userprefs.landingPage;
} else {
var permissionRoutes = {
'regimens': 'regimens.do',
'pathways': 'pathways',
'manage.users': 'manageusers.do',
'manage.practices': 'managepractices.do',
'patients': 'patients'
};
_.some(_permissions, function (el) {
var state = $state.get(permissionRoutes[el]);
if (!state.abstract) {
page = state.name;
return true;
}
});
}
return page;
};
function broadcastLogin() {
$rootScope.$broadcast('application:loggedinstatus');
}
broadcastLogin();
return service;
}
])
在我采取一组非常具体的操作之前,这段代码工作得很好:
- 登录
- 关闭打开的选项卡或window
- 打开新标签并转到应用程序
因为我仍然登录到应用程序,所以我有一个用户对象和一个有效令牌,但我得到 error:infdig Infinite $digest Loop
。它最终解决并进入正确的状态,但需要一段时间并且路径闪烁(如果需要,我可以 post 视频)。
我尝试在 $rootScope.$on('$stateChangeSuccess')
回调中使用 $location.path
而不是 $state.go
,但问题仍然存在。
这并没有真正影响应用程序的运行,但很烦人。我也真的不想将我的储物柜存储更改为会话存储,因为我希望用户在关闭选项卡并重新打开时保持登录状态。
这通常发生在应用程序卡在通过 resolve 子句的路由拒绝和上一个路由上的自动重定向之间时,登录页面将重定向到某个页面,比如 auth,而 auth 页面需要一些条件才能让你进入,如果它失败或者它会重定向回其他页面,因此循环,确保你的故事是直截了当的,如果需要使用中间状态来清除所有偏好并采用默认路径
我想说的是,这个问题隐藏在 if
语句中 $rootScope.$on('$stateChangeStart'...
检查这个:
- Ui-Router $state.go inside $on('$stateChangeStart') is cauzing an infinite loop
一般建议:
let's redirect ($state.go()) only if needed - else get out of the event listener
$rootScope.$on('$stateChangeStart' ...
if (toState.name === 'login' ){
// going to login ... do not solve it at all
return;
}
第二次检查应该是:用户是否已通过身份验证(并且不会登录)?
if(AppService.getIsLoggedIn()) {
// do not redirect, let him go... he is AUTHENTICATED
return;
}
现在我们有状态,不是登录,用户未认证,我们可以清楚地调用:
// this is a must - stop current flow
e.preventDefault();
$state.go('login'); // go to login
一切都会像我们预期的那样工作
非常详细的解释和working example could be also found here...