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;
}
},
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 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;
});
}
}
I'm not sure if this is a duplicate or not, but I didn't manage to find anything that worked for me, so I'm posting this question.
I have a situation where I need to get values from database before directing user to certain routes, so I could decide what content to show.
If I move e.preventDefault() right before $state.go(..) then it works, but not properly. Problem is that it starts to load default state and when it gets a response from http, only then it redirects to main.home. So let's say, if the db request takes like 2 seconds, then it takes 2 seconds before it redirects to main.home, which means that user sees the content it is not supposed to for approximately 2 seconds.
Is there a way to prevent default at the beginning of state change and redirect user at the end of state change?
Also, if we could prevent default at the beginning of state change, then how could we continue to default state?
(function(){
"use strict";
angular.module('app.routes').run(['$rootScope', '$state', '$http', function($rootScope, $state, $http){
/* State change start */
$rootScope.$on('$stateChangeStart', function(e, to, toParams, from, fromParams){
e.preventDefault();
$http
.get('/url')
.error(function(err){
console.log(err);
})
.then(function(response){
if( response.data === 2 ){
// e.preventDefault()
$state.go('main.home');
}
// direct to default state
})
}
}]);
});
You could add a resolve section to your $stateProviderConfig.
Inside the resolve you can make a request to the databse and check required conditions. If case you don't want user to acces this page you can use $state.go() to redirect him elsewhere.
Sample config:
.state({
name: 'main.home',
template: 'index.html',
resolve: {
accessGranted: ['$http', '$state', '$q',
function($http, $state, $q) {
let deffered = $q.defer();
$http({
method: 'GET',
url: '/url'
}).then(function(data) {
if (data === 2) {
// ok to pass the user
deffered.resolve(true);
} else {
//no access, redirect
$state.go('main.unauthorized');
}
}, function(data) {
console.log(data);
//connection error, redirect
$state.go('main.unauthorized');
});
return deffered.promise;
}
]
}
});
Documentation of the resolve is available here
Note that you could use Promise object instead of $q service in case you don't need to support IE
One way to handle this situation is adding an interceptor as follows.
.config(function ($httpProvider) {
$httpProvider.interceptors.push('stateChangeInterceptor');
}).factory('stateChangeInterceptor', function ($q, $window,$rootScope) {
return {
'response': function(response) {
var isValid = true;//Write your logic here to validate the user/action.
/*
* Here you need to allow all the template urls and ajax urls which doesn't
*/
if(isValid){
return response;
}
else{
$rootScope.$broadcast("notValid",{statusCode : 'INVALID'});
}
},
'responseError': function(rejection) {
return $q.reject(rejection);
}
}
})
Then handle the message 'notValid' as follows
.run(function($state,$rootScope){
$rootScope.$on("notValid",function(event,message){
$state.transitionTo('whereever');
});
})
In my scenario, when a visitor navigates to a page (or route) of the first time they should be anonymously authenticated (I'm using Firebase). For context; later on the visitor may migrate their anonymous session after they have logged in, with Facebook, for example.
If the anonymous authentication fails for some reason, they are redirected to an error page (route) -- one of few pages that do not require any authentication.
I am using promises to:
check if the visitor is already authenticated
if they are not, try and anonymously authenticate them
if they are authenticated successfully, resolve the promise (and route)
if the authentication fails for some reason, reject the promise
Question:
The promise always seems to be rejected the first time a visitor navigates to a page (route) that requires authentication, even when the visitor has been anonymously authenticated successfully (in step 2 above); why would this be happening?
Below I have included my code, and added a comment to highlight the section that seems to be causing the problem.
Thanks for your help with this, it is always appreciated!
var app = angular.module('vo2App', ['firebase', 'ngCookies', 'ngRoute']);
app.config(['$locationProvider', '$routeProvider', function ($locationProvider, $routeProvider) {
$routeProvider
.when('/', {
controller: 'HomeCtrl',
templateUrl: '/views/home.html'
})
.when('/login', {
controller: 'LoginCtrl',
templateUrl: '/views/login.html'
})
.when('/oops', {
controller: 'OopsCtrl',
resolve: {
currentAuth: function (){
return null;
}
},
templateUrl: '/views/oops.html'
});
}]);
app.run(['$location', '$rootScope', 'Auth', 'ErrorMsg', function ($location, $rootScope, Auth, ErrorMsg) {
$rootScope.$on('$routeChangeError', function (event, next, prev, error) {
if (error === 'AUTH_REQUIRED') {
ErrorMsg.add('Unauthorized');
// TODO: Make all error messages constants
$location.url('/oops');
}
});
$rootScope.$on('$routeChangeStart', function (event, next, current) {
if (! ('resolve' in next)) {
next.resolve = {};
}
if (! ('currentAuth' in next.resolve)) {
next.resolve.currentAuth = function ($q, Auth) {
var deferred = $q.defer();
Auth.$requireAuth().then(deferred.resolve, function () {
/* ** The following line seems to be causing the problem ** */
Auth.$authAnonymously().then(deferred.resolve, deferred.reject('AUTH_REQUIRED'));
});
return deferred.promise;
};
}
});
}]);
deferred.reject is not passed to then as error function, it is called every time.
Auth.$authAnonymously().then(deferred.resolve, deferred.reject('AUTH_REQUIRED'));
That's why deferred.promise is always rejected.
And you should possibly know that the code above is usually referred to as deferred antipattern. It is preferable to use existing promises instead of creating a new one with defer().
I'm struggling to get Angular route resolve working. I find the documentation less than useless for more complex parts of the javascript framework like this.
I have the following service:
app.service("AuthService", ["$http", "$q", function($http, $q){
this.test = function(){
return $q(function(resolve, reject){
var auth = false;
resolve(auth);
});
}
}]);
Now my routes looks like this:
app.config(["$routeProvider", "$locationProvider", function($routeProvider, $locationProvider){
$locationProvider.html5Mode(true).hashPrefix("!");
$routeProvider
.when("/account", {
templateUrl: "/views/auth/account.html",
controller: "AccountController",
resolve: {
auth: ["AuthService", function(AuthService) {
return AuthService.test().then(function(auth){
if (auth) return true;
else return false;
});
}]
}
});
}]);
What I want to happen here is the following:
User goes to /account
AuthService is fired and returns a variable (true or false)
In the resolve, if the returned value is false, the route cannot be loaded
If the returned value is true, the user is authenticated and can view the route
I don't think I've fully understood how to use resolve and my method so far does not work. Could someone please explain the correct way to do this?
The resolve block when configuring routes is designed to make navigation conditional based upon the resolution or rejection of a promise.
You are attempting to handle this by resolving with a resolution value of true or false
Please try the following:
resolve: {
auth: ["AuthService", function(AuthService) {
return AuthService.test().then(function(auth){
if (!auth){
throw 'not authorized';
}
});
}]
}
This will cause the promise to be rejected and therefore not allow the routing to continue/complete.
Also of note is that the value coming out of the promise resolution will be injected into the handling controller
This solution works for me, I also tried with .then function but it's incorrect because resolve performs it.
resolve: {
auth:
["AuthService", "AnotherService", "$rootScope", function(AuthService, AnotherService, $rootScope) {
if ($rootScope.yourCondition){
return AuthService.getFunction();
}
else{
return AnotherService.getAnotherFunction();
}
}]
},
views: {
'partialView#': {
/* == Component version == */
component: "yourComponent",
bindings: {
auth: 'auth', // Inject auth loaded by resolve into component
params: '$stateParams'
}
}
}