I'm trying to use meteor angular js ui-router resolve to load information of one user selected from user list.
$stateProvider
.state('userprofile', {
url: '/user/:userId',
cache: false,
template: '<user-profile userinfo="$resolve.userinfo"></user-profile>',
controller: UserProfile,
controllerAs: name,
resolve: {
userinfo: function($stateParams) {
viewedUser = Meteor.users.findOne({
_id: $stateParams.userId
});
return viewedUser;
},
}
});
The problem is that, for the first time after from user list, user profile display correctly. However, page reload makes the userinfo becomes undefined.
I guest that from second time, the controller loaded already so that it display before resolve done?!
After a while searching, I tried $q and $timeout
resolve: {
userinfo: function($stateParams, $q, $timeout) {
deferred = $q.defer();
$timeout(function() {
deferred.resolve(Meteor.users.findOne({
_id: $stateParams.userId
}));
}, 1000);
return deferred.promise;
},
}
It works as I expected, user profile displayed every time I refresh the page.
But if I lower the delay to 500, it back to undefined when refreshed.
I not sure why in this case, longer delay works?
Thank you!
Here is the code that I use,
resolve: {
currentUser: ($q) => {
var deferred = $q.defer();
Meteor.autorun(function () {
if (!Meteor.loggingIn()) {
if (Meteor.user() == null) {
deferred.reject('AUTH_REQUIRED');
} else {
deferred.resolve(Meteor.user());
}
}
});
return deferred.promise;
}
}
This is from a tutorial by #urigo somewhere, which took me some time to find, but it works like a charm.
This code is handy to trap the case where authentication is required - put it at the top level in a .run method
function run($rootScope, $state) {
'ngInject';
$rootScope.$on('$stateChangeError',
(event, toState, toParams, fromState, fromParams, error) => {
console.log("$stateChangeError: "+error);
if (error === 'AUTH_REQUIRED') {
$state.go('login');
}
}
);
}
You can try this routes in resolve
if you use angular-meteor
resolve: {
'loginRequired': function ($meteor, $state) {
return $meteor.requireUser().then(function (user) {
if (user._id) {return true;}
}).catch(function () {
$state.go('login');
return false;
});
}
}
Related
Im just learning ui-router resolve and would like to simply redirect my state if the user is not logged-in.
It seems I cannot simply use $state.go inside the callback function.
Here is my code:
.state('base.user', {
url: '/user',
templateUrl: 'views/user.html',
controller: 'userCtrl',
resolve: {
test: function($state, $q) {
var deferred = $q.defer();
if (!loggedIn()) { // resolves to false when not logged-in
deferred.reject();
$state.go('base.test'); // ** Throws "Possibly unhandled rejection" error
} else {
deferred.resolve();
/* and maybe do some more stuff.. */
}
}
}
})
I know this is often done with services and things like that, but for now, I would just like a simple working example.
The way we decided to handle it was to listen to $stateChangeError and if the error thrown contained a path, then go to that path.
$rootScope.$on('$stateChangeError', function(toState, toParams, fromState, fromParams, error) {
if (error.state) {
$state.go(error.state, error.stateParams);
}
});
And in you resolve:
test: function($q) {
var deferred = $q.defer();
if (!loggedIn()) {
deferred.reject({state: 'base.test'});
} else {
deferred.resolve();
}
return deferred.promise;
}
Implement the $stateChangeStart hook and check your redirection condition there
$rootScope.$on('$stateChangeStart', function (event, toState) {
if (toState.name === 'base.user') {
if(!loggedIn()) { // Check if user allowed to transition
event.preventDefault(); // Prevent migration to default state
$state.go('base.test');
}
}
});
I'm using app.run() in my AngularJS app to check whether a user is logged in before displaying the site to block access to various sites for non-registered users. I tried doing it with a promise because before, whenever I reloaded the page the isLoggedIn function would return false the getStatus hasn't returned the answer from the server yet.
Now using the promise, the site just calls itself in a loop forever, I guess because the process just repeats itself when the promise is resolved. Where am I going wrong and how could I fix this? Thanks in advance, help is much appreciated!
This is my code in app.js:
app.run(function($rootScope, $state, authService){
$rootScope.$on('$stateChangeStart', function(event, next, nextParams, from, fromParams){
event.preventDefault();
authService.getUserStatus().then(function(){
console.log(authService.isLoggedIn());
if(next.access.restricted && !authService.isLoggedIn()){
$state.go('index', {}, { reload: true });
} else {
$state.go(next, {}, { reload: true });
}
});
});
});
Here's the service authService.js:
(function(){
var app = angular.module('labelcms');
app.factory('authService', ['$q', '$timeout', '$http', function($q, $timeout, $http){
var user = null;
var isLoggedIn = function(){
if(user){
return true;
} else {
return false;
}
};
var getUserStatus = function(){
var deferred = $q.defer();
$http.get('/api/user/status')
.success(function(data){
if(data.status){
user = data.status;
deferred.resolve();
} else {
user = false;
deferred.resolve();
}
})
.error(function(data){
console.log('Error: ' + data);
user = false;
deferred.resolve();
});
return deferred.promise;
};
return ({
isLoggedIn: isLoggedIn,
getUserStatus: getUserStatus,
login: login,
logout: logout,
signup: signup
});
}]);
})();
It loops because every time you execute $state.go(next, {}, { reload: true }); it will hit your $rootScope.$on again.
I would check if we actually are on restricted route before you go into your security service.
app.run(function($rootScope, $state, authService){
$rootScope.$on('$stateChangeStart', function(event, next, nextParams, from, fromParams){
if(!next.access.restricted) return;
authService.getUserStatus().then(function(){
console.log(authService.isLoggedIn());
if(!authService.isLoggedIn()){
$state.go('index', {}, { reload: true });
});
});
});
I'm trying to build some sort of authentication in my angular app and would like to redirect to a external URL when a user is not logged in (based on a $http.get).
Somehow I end up in an infinite loop when the event.preventDefault() is the first line in the $stateChangeStart.
I've seen multiple issues with answers on stackoverflow, saying like "place the event.preventDefault() just before the state.go in the else". But then the controllers are fired and the page is already shown before the promise is returned.
Even when I put the event.preventDefault() in the else, something odd happens:
Going to the root URL, it automatically adds the /#/ after the URL and $stateChangeStart is fired multiple times.
app.js run part:
.run(['$rootScope', '$window', '$state', 'authentication', function ($rootScope, $window, $state, authentication) {
$rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
event.preventDefault();
authentication.identity()
.then(function (identity) {
if (!authentication.isAuthenticated()) {
$window.location.href = 'external URL';
return;
} else {
$state.go(toState, toParams);
}
});
});
}]);
authentication.factory.js identity() function:
function getIdentity() {
if (_identity) {
_authenticated = true;
deferred.resolve(_identity);
return deferred.promise;
}
return $http.get('URL')
.then(function (identity) {
_authenticated = true;
_identity = identity;
return _identity;
}, function () {
_authenticated = false;
});
}
EDIT: Added the states:
$stateProvider
.state('site', {
url: '',
abstract: true,
views: {
'feeds': {
templateUrl: 'partials/feeds.html',
controller: 'userFeedsController as userFeedsCtrl'
}
},
resolve: ['$window', 'authentication', function ($window, authentication) {
authentication.identity()
.then(function (identity) {
if (!authentication.isAuthenticated()) {
$window.location.href = 'external URL';
}
})
}]
})
.state('site.start', {
url: '/',
views: {
'container#': {
templateUrl: 'partials/start.html'
}
}
})
.state('site.itemList', {
url: '/feed/{feedId}',
views: {
'container#': {
templateUrl: 'partials/item-list.html',
controller: 'itemListController as itemListCtrl'
}
}
})
.state('site.itemDetails', {
url: '/items/{itemId}',
views: {
'container#': {
templateUrl: 'partials/item-details.html',
controller: 'itemsController as itemsCtrl'
}
}
})
}])
If you need more info, or more pieces of code from the app.js let me know !
$stateChangeStart will not wait for your promise to be resolved before exiting. The only way to make the state wait for a promise is to use resolve within the state's options.
.config(function($stateProvider) {
$stateProvider.state('home', {
url: '/',
resolve: {
auth: function($window, authentication) {
return authentication.identity().then(function (identity) {
if (!authentication.isAuthenticated()) {
$window.location.href = 'external URL';
}
});
}
}
});
});
By returning a promise from the function, ui-router won't initialize the state until that promise is resolved.
If you have other or children states that need to wait for this, you'll need to inject auth in.
From the wiki:
The resolve keys MUST be injected into the child states if you want to wait for the promises to be resolved before instantiating the children.
I'm using this tutorial to figure out my authentication system for a web app that I am working on. I'm using ui-router's StateProvider and resolve system to reroute the user to the home page if they attempt to access one of the pages that needs authentication. Everything seems to be working, except that the resolve part doesn't seem to be actually working - i.e. my authenticate returns a rejected promise, yet the page loads like normal, despite the fact that there should be some sort of error because of this. What am I doing wrong?
app.states.js
angular
.module('app')
.config(routeConfig);
/** #ngInject */
function routeConfig($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/');
// checks if user is logged in or not
// passes back rejected promise if not, resolved promise if true
function authenticated(authFactory, $q) {
var deferred = $q.defer();
authFactory.authenticate()
.then(function(authenticate) {
if (authenticate.data === 'true') {
deferred.resolve();
} else {
deferred.reject();
}
});
return deferred.promise;
}
// every new state that should include a sidebar must have it as a view
$stateProvider
.state('dashboard', {
url: '/dashboard/',
views: {
'sidebar': {
templateUrl: 'app/components/navbar/sidebar.html',
controller: 'SidebarController as vm'
},
'content': {
templateUrl: 'app/components/authenticated/dashboard.html',
controller: 'DashboardController as vm'
}
},
resolve: {
authenticated: authenticated
}
})
app.run.js
function runBlock($rootScope, $log, $state) {
$rootScope.$on('$stateChangeError', function () {
// Redirect user to forbidden page
$state.go('forbidden');
});
}
auth.factory.js
'use strict';
angular
.module('app')
.factory('authFactory', authFactory);
authFactory.$inject = ['$http', '$cookies'];
function authFactory($http, $cookies) {
var _token;
var service = {
authenticate: authenticate
};
return service;
// used to prevent user from accessing pages that they shouldn't have access to
// this is used exclusively in app.routes.js/app.states.js
function authenticate() {
// gets user's token from cookies, if no cookie, _token will be blank and server will return 403
// this part might be redundant with other functions, but I left it in for now just to make sure
if ($cookies.getObject('user')) {
_token = $cookies.getObject('user').token;
} else {
_token = '';
}
var request = $http({
method: 'POST',
url: 'http://localhost:8080/checkToken',
headers: {'x-auth-token': _token},
transformResponse: function(data) {
return data;
}
});
return request;
}
}
You need to place return deferred.promise outside then function, so that promise will get returned properly.
Code
function authenticated(authFactory, $q, $log) {
var deferred = $q.defer();
authFactory.authenticate()
.then(function(authenticate) {
if (authenticate.data === 'true') {
deferred.resolve();
} else {
deferred.reject();
}
});
return deferred.promise; //placed outside function
}
I'm using ui-router in my angular application. Currently I've two routes /signin & /user.
Initially it shows /signin when the user clicks on the login button, I'm sending a ajax request and getting the user id. I'm storing the user id in localstorage and changing the state to /user.
Now, what I want, if a user is not loggedin, and user changes the addressbar to /user, it'll not change the view, instead it'll change the addressbar url to /signin again.
I'm try to use resolve, but it's not working. My code is:-
module.exports = function($stateProvider, $injector) {
$stateProvider
.state('signin', {
url: '/signin',
template: require('../templates/signin.html'),
controller: 'LoginController'
})
.state('user', {
url: '/user/:id',
template: require('../templates/user.html'),
resolve:{
checkLogin: function(){
var $state = $injector.get('$state');
console.log("in resolve");
if (! window.localStorage.getItem('user-id')) {
console.log("in if")
$state.go('signin');
}
}
},
controller: 'UserController'
})
}
Please help me to solve this problem.
I don't think it's allowed to change states in the middle of a state transition.
So, the way to address it is to have the checkLogin resolve parameter (I changed it below to userId) to be a function that either returns a value or a promise (in this case, a rejected promise, if you can't get the user-id).
You'd then need to handle this in $rootScope.$on('$stateChangeError') and check the error code.
resolve: {
userId: function ($q, $window) {
var userId = $window.localStorage.getItem('user-id');
if (!userId) {
return $q.reject("signin")
}
return userId;
}
}
And redirect in the $stateChangeError handler:
$rootScope.$on('$stateChangeError', function (event, toState, toParams, fromState, fromParams, error) {
if (error === "signin") {
$state.go("signin");
}
});
If someone has this problem, you can solve it, using timeout service. It will put state switching call at the end of queue.
Also, you should use promises. Rejecting it will prevent initialization of that state:
resolve:{
checkLogin: function(){
var deferred = $q.defer();
var $state = $injector.get('$state');
if (!window.localStorage.getItem('user-id')) {
$timeout(function(){$state.go('signin');});
deferred.reject();
} else {
deferred.resolve();
}
return deferred.promise;
}
},