currently i'm developing a sample admin application using angularjs, in my application. i have used ui-router instead of ngRoute to define the urls. on my config function states are defined as below.
sampleModule.config(['$stateProvider','$urlRouterProvider',function ($stateProvider,$urlRouterProvider) {
$urlRouterProvider.otherwise('login');
$stateProvider
.state('login',{
url:'/login',
templateUrl: 'views/html/login.html',
controller: 'authCtrl'
}).state('main', {
url:'/main',
templateUrl: 'views/html/main.html',
controller: 'adminViewCtrl'
});
}]);
in the run state of the application i'm redirecting the users to their respective view as follows.
sampleModule.run(function ($rootScope, AuthService, $state) {
$rootScope.$on("$stateChangeStart", function (event) {
if (AuthService.isAuthenticated()) {
event.preventDefault();
$state.go('main');
} else {
event.preventDefault();
$state.go('login');
}
});
});
but the problem i'm facing is application giving following exception and crashes some times when URL changes.
RangeError: Maximum call stack size exceeded
I put a debug point to run function of the application and
and noticed that error coming because content inside if (AuthService.isAuthenticated()) { condition executing in a infinite loop.
below is the image for the errors in chrome developer tools
i'm confused about the things happening here, i Googled for few hours and was not able to come up with a straight answer, i even tried putting event.preventDefault() as some suggestions i saw , but it didn't helped to resolve the problem.
i'm wondering what error i'm doing here? is there any better way to restrict user access to the log-in page,or some other parts of the application after logging in to the application?
The problem here is that you are constantly redirecting user to same state. Here is what happens: user goes to some state, $stateChangeStart is triggered, you redirect user again to some other state, and again $stateChangeStart is triggered, you check authentication and redirect again, and that continues until RangeError appears. What you should probably do is only redirect user when he is unauthorized, if he is authorized, let him see the page he is looking for.
sampleModule.run(function ($rootScope, AuthService, $state) {
$rootScope.$on("$stateChangeStart", function (event, toState) {
if (!AuthService.isAuthenticated() && toState.name !== 'login') {
$state.go('login');
} else if(AuthService.isAuthenticated() && toState.name === 'login') {
$state.go('main');
}
});
});
Here you check if user is authorized and if navigation state is not login (since he already got redirected) and later check if it's login page he is wanting for and redirecting to main
Related
I have an angular js module where all the routes are setup. I have defined a variable "maintenance". if this is set to true , I want the page to be redirected to maintenance page. I have setup the states using stateprovider.
I am trying to redirect using the code below -
if(maintenance){
$state.go('maintenance');
}
This doesn't seem to work. However If I do the below , the redirect is successful -
$urlRouterProvider.otherwise('/signup/productSelector');
I assume using "otherwise" may not be the correct solution in this case. How can I redirect?
EDIT
In the below example , I would like any call to app.html to be redirected to maintenance page irrespective of what is present after #.
https://<url>/app.html#/orders/resi
You cannot use the state service within the config method since it is still being configured at that point.
If you'd like to specificly redirect right after the angular module is run then you could execute the $state.go in a .run function as follows:
angular.module("yourModule").run(['$state', function($state) {
$state.go('maintenance');
}])
Or better yet you can force the redirection to happen after every state transition using the transition services:
angular.module("yourModule").run(['$transition', function($transition) {
$transition.onBefore({}, function(transition) {
var stateService = transition.router.stateService;
if (maintenance && transition.to().name !== 'maintenance') {
return stateService.target('maintenance');
}
})
}])
https://ui-router.github.io/guide/transitionhooks
You cannot use the state service within a configure method. Instead if you'd like to redirect to a certain state after the angular module has been loaded you could do it in a .run function insteadn
angular.module().run(['$state' '$rootScope', function($state, $rootScope) {
$rootScope.$on('$stateChangeStart', function(e, toState, toParams, fromState, fromParams) {
if (maintanance) {
// If logged out and transitioning to a logged in page:
e.preventDefault();
$state.go('maintenance');
}
});
I have a web app which uses ui-router for routing. There is a user management module where there are certain roles given to a user (role x, y and z). When a user with role x logs in he will be restricted to certain states of the ui-router e.g. 'dashboard'.
When a user logs in I have saved the user's role in a cookie variable (using $cookie). I also used ng-hide to hide the Dashboard navigation sidebar when user with role x is logged in like this:
HTML:
<li ui-sref-active="active">
<a ui-sref="dashboard" ng-hide="roleIsX()">
<i class="fa fa-laptop"></i> <span class="nav-label">Dashboard</span>
</a>
</li>
Controller:
$scope.roleIsX = function() {
if ($cookies.get('loggedInUserRole') === "x")
return true;
else
return false;
}
This works okay for now but the problem is when any user logs in he is able to directly navigate to a URL by writing it in the address bar. Is there an easy way I could solve this issue keeping in view my situation?
I eventually used resolve of ui-router as #Shivi suggested. Following is the code if anyone is still looking up:
app.config(['$urlRouterProvider', '$stateProvider', '$locationProvider',
function($urlRouterProvider, $stateProvider, $locationProvider){
...
.state('user-management', {
...
...
resolve: {
roleAuthenticate: function($q, UserService, $state, $timeout) {
if (UserService.roleIsX()) {
// Resolve the promise successfully
return $q.when()
} else {
$timeout(function() {
// This code runs after the authentication promise has been rejected.
// Go to the dashboard page
$state.go('dashboard')
})
// Reject the authentication promise to prevent the state from loading
return $q.reject()
}
}
}
})
This code checks if the roleIsX then continue to the state else open dashboard state.
Helpful Reference: angular ui-router login authentication - Answer by #MK Safi
listen to the $locationChangeStart event, and prevent it if needed:
$scope.$on("$locationChangeStart", function(event, next, current) {
if (!$scope.roleIsX()) {
event.preventDefault();
}
});
A redirect to the login page should happen if a user has not authenticated yet. $state.go("login"); should accomplish this, but it doesn't do anything.
// in app.js
$stateProvider
.state('login', {
template: 'login.html'
url: '/login',
controller: 'AuthenticationCtrl',
})
.state('special', {
resolve: { loginRequired: checkAuthenticated },
template: 'special.html',
url: '/special',
controller: 'SpecialCtrl',
})
var checkAuthenticated = function($state, AuthenticationService, $q, $timeout) {
var deferred = $q.defer();
if(AuthenticationService.isAuthenticated()) {
deferred.resolve();
} else {
deferred.reject();
alert('You are being redirected to login page');
$state.go("login"); // redirect to login page -- but this does nothing.
}
}
Here, if a user has not authenticated and tries to access a route that requires authentication, I see my alert pop up. But nothing happens afterwards; ui-router doesn't redirect to my login page.
I also tried out $location.path('/login') instead of $state.go("login"), but this also does not do anything.
I checked the documentation it says for go():
String Absolute State Name or Relative State Path
Which I have, so I must be missing something that's causing this problem, but I'm not sure what.
I've experienced issues with state go in conjunction with an alert, I solved this by using a $timeout set to 100ms or so, eg: delay your state.go and hopefully this will solve your issue.
I am using the following code to resolve a resource when the main state is loaded. Is it possible to re - resolve the resource without reloading the page? I am avoiding reload so that the user experience is not affected.
$stateProvider
.state('main', {
url: '/',
templateUrl: 'publicApp/main.html',
controller: 'MainCtrl as mainCtrl',
resolve: {
userData: ["UserApi", function (UserApi) {
return UserApi.getUserData().$promise;
}]
}
})
.controller('MainCtrl', function (userData) {
console.log(userData.something);
})
Since is a public site, the user can visit any page without logging in but when the user logs in the page must customize based on the user data.
EDIT
I am using a modal for login so the state doesn't reload after logging in, I was thinking of throwing an event on $rootScope and then add listeners in controllers to load them again. But this doesn't look good, so I am looking for a better way
I currently have two options:
Reload page - will effect the user experience, so its the last option
Throw an event for the login modal and catch it in other controllers
Any better ideas?
Try using state reload once promise is resolved
$state.reload();
How can I redirect to the login page if someone tries to hit any other route when they are not authenticated? Is there a "best" way to do this in AngularJS?
Seems like a common problem but I can't seem to find a way to do this. Thank you in advance for your help.
The best way to do this is to set up a '$routeChangeStart' listener which checks an 'authProvider' service function to verify that there is a user logged in. In our 'app.js' or in a separate file:
angular.module('myApp')
.run(['$rootScope', '$location', 'authProvider', function ($rootScope, $location, authProvider) {
$rootScope.$on('$routeChangeStart', function (event) {
if (!authProvider.isLoggedIn()) {
console.log('DENY : Redirecting to Login');
event.preventDefault();
$location.path('/login');
}
else {
console.log('ALLOW');
}
});
}])
Then for our 'authProvider' service:
angular.module('myApp')
.factory('authProvider', function() {
var user;
return {
setUser : function(aUser){
user = aUser;
},
isLoggedIn : function(){
return(user)? user : false;
}
};
});
This solution was created from an answer here on stack overflow.
Thank you #MohammadAwwaad
I am doing this a different way now, with Node.js & Express & the passport module, but before that, when I was using PHP, I did it with several Angular modules. I had an outside module on my <html> tag with ng-app, then ng-controllers at certain tags, like <body> & certain <div>s. In one of these ng-controllers, I had an authentication function, then I had an ng-if for login, logout, etc. If the user is not logged in, I hid the current page and ng-included the appropriate page. Otherwise, I ng-included the current page.
That was probably not the best solution, but I didn't want to use third-party modules. If you get confused or have any questions (I know I was pretty confused) just ask.