locationChangeStart and preventing route change - angularjs

I'm trying to create basic validation whether user can access some route or not.
I had progress in that, but there's one thing that I can't figure out.
I'm using $locationChangeStart to monitor route changes. Scenario is:
1. if user is logged in, then allow him to access all routes, except auth routes (login, register). I'm checking this by calling method isAuthenticated() from my AuthFactory
2. If user is not logged in, then he access only login and register routes. Any other route should be prevented, and user should be redirected to login in that case.
$rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl){
if(AuthFactory.isAuthenticated()){
if(AuthFactory.isAuthRoute(newUrl)){
event.preventDefault();
$location.path('/');
}
} else {
if(!AuthFactory.isAuthRoute(newUrl)){
event.preventDefault();
$location.path('/login');
}
}
});
Thing that troubles me, is the one with preventDefault(). If app reaches code with preventDefault(), location.path() that comes after that, simply doesn't work.
However, if I remove event.preventDefault(), location.path() works. Problem with this, is that I need that prevent, in case non-logged tries to access some non-auth page.
Basically, I want to be able to prevent or redirect based on requested route. What is the proper way to do that?

Ok, you need to do this:
var authPreventer = $rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl){
if(AuthFactory.isAuthenticated()){
if(AuthFactory.isAuthRoute(newUrl)){
event.preventDefault();
authPreventer(); //Stop listening for location changes
$location.path('/');
}
}
else {
if(!AuthFactory.isAuthRoute(newUrl)){
event.preventDefault();
authPreventer(); //Stop listening for location changes
$location.path('/login');
}
}
});

You can try using auth in resolves in-order to prevent access to certain routes.
here is the doc,it's not very clear, but you can find plenty of examples out there.

I recently had the same problem and I was finally able to solve it by listening to $routeChangeStart instead of $locationChangeStart (without needing to call $route.reload()).
The documentation for both events is kinda vague... I suppose the $ruteChangeStart event is called before the $locationChangeStart (I'm going to read the source code to fully understand what's happening here).

Ok, I managed to do this using $routeChangeStart. The catch is in using $route.reload(). So above code, should look something like this:
$rootScope.$on('$routeChangeStart', function(event, next, current){
if(AuthFactory.isAuthenticated()){
if(AuthFactory.isAuthRoute(next.originalPath)){
$route.reload();
$location.path('/');
}
} else {
if(!AuthFactory.isAuthRoute(next.originalPath)){
$route.reload();
$location.path('/login');
}
}
});
I put this in my .run method, so all the request are handled here, and I don't need to think about every new route that I (or someone else adds). That's why this looks more clean to me.
If someone has different approach, please share.
Note: just in case, I do my check on backend part also :)

Related

How to get requested route in angular when otherwise was used

I trying to determine what the original route was when angular defaulted to the .otherwise route.
I can't see any information in the documentation on this.
Now I am considering creating a service that intercepts the broadcast for route change start and storing that value. But this seems overkill for something so simple.
So is there a better way to determine this information?
You can use the $routeChangeSuccess event and get the previous route in one of the callback parameters of this event.
PLUNKER
e.g.
app.run(function($rootScope) {
$rootScope.$on('$routeChangeSuccess', function(event, current, previous) {
if(previous && previous.$$route) {
console.log(previous.$$route); // displays previous route
}
});
});
As a reference regarding its documentation, you can see this question I've answered.

Infinite Loop on ui-router's $stateChangeStart

Ramping up on angular, and ui-router
And struggling with redirecting to a different state if a precondition is not met:
I tried using an interceptor: (How do I preform a redirect in an angular interceptor).
But someone mentioned that handling $stateChangeState would be more appropriate. But I am still running into an infinite loop:
/**
* Check here for preconditions of state transitions
*/
$rootScope.$on('$stateChangeStart', function(event, toState) {
// which states in accounts to be selected
var accountRequiredStates = ['user.list', 'user.new'];
if(_.contains(accountRequiredStates, toState.name)){
event.preventDefault();
ApiAccount.customGET('get_current').then(function(resp){
// if I have a selected account, go about your business
if(resp.hasOwnProperty('id')){
$state.go(toState.name);
} else { // prompt user to select account
$state.go('user.select_account');
}
})
}
});
Can anyone suggest a better pattern (one that works)
Thanks!
Note: Similar problem different approach here: How do I preform a redirect in an angular interceptor
I don't think there's anything wrong with the general way you're trying to do this, though I'm not an expert. I do see a flaw in the implementation which looks like it could cause an infinite loop. Let's say the user tries to go to 'user.new' state. Your $stateChangeStart listener intercepts that, cancels it, does your customGET; then if the inner if condition is true (resp.hasOwnProperty('id')), you try to send the user to the same 'user.new' state. At which point, your $stateChangeStart listener intercepts it, cancels it, etc., over and over.
The way I avoid this problem in my code is to have a variable (in the service where I declare the listener) to help me bypass that check:
var middleOfRedirecting = false; Inside your inner if block within the resp.hasOwnProperty('id') check, set middleOfRedirecting to true; add a condition at the start of your $stateChangeStart listener to only call event.preventDefault() and redirect if middleOfRedirecting is false. You also would need a $stateChangeSuccess listener to set middleOfRedirecting back to false, resetting it for the next state change. (I feel like there should be a better way than this, but it at least works.)

AngularJS: Understanding $rootScope.$on('$routeChangeSuccess

I am working on a login page, on success, it redirect to home page. By default I show login page this code:
app.run(function($rootScope, $location) {
$rootScope.$on('$routeChangeSuccess', function() {
$location.url("/login");
});
});
Then after validating the user/pass details from the backend I take the user to the home page:
$scope.login = function() {
if ($scope.username === 'admin' && $scope.password === 'pass') {
console.log('successful')
$rootScope.$on('$routeChangeSuccess', function() {
$location.url("/home")
});
$location.url("/blah");
} else {
$scope.loginError = "Invalid username/password combination";
console.log('Login failed..')
};
};
The redirect doesn't seem to work if I remove the second $location.url before the else section of the if statement. It is however not using that url (/blah), it goes to home. but If url blah is removed it the redirect logic does not work.
I can't seem to understand why I have to use two $location.url(). I would appretiate if someone can help me understand how this redirect system works?
This might not be the best practice, I am open to suggestions on how to improve this, here is Plunker example
All in all this is going down a wrong path IMO...
Obviously you need to lock down any resources server side as client side can always be "changed" in a simple debugger... But I guess you already know that...
Alternative routing solutions like https://github.com/dotJEM/angular-routing or https://github.com/angular-ui/ui-router IMO gives you some better handles for this, but lets just evaluate some approaches...
One would be: http://plnkr.co/edit/ZUKB1v?p=preview Although that requires you resolve the user on all routes... So.. :(...
Another would be: http://plnkr.co/edit/iM9cA1?p=preview which might be a little better...
Finally, what people often seem to do is provide http interceptors that redirects to the login page when a "Unauthorized" error code is returned from the server. But this could seem to be a more advanced approach than your ready for.

close/leave page event with AngularJS

I am trying to implement a single page application that starts with a login form. For front-end I'm using AngularJS.
In the login's controller I check the authentication data, and if it is ok, I set a cookie (using $cookieStore service) with some user data and route to another view. Here, in the controller of the second view I check if the user from the cookie is empty and if yes, I redirect to the login view.
What I want to do is when the user close the browser, or if leaves the page the cookie to be removed.
I have tried to use:
$scope.$on("$locationChangeStart", function(){
$cookieStore.remove('user');
});
or
$scope.$on("$destroy", function(){
$cookieStore.remove('user');
});
but it does not work.
I want to avoid the scenario when a user log in (the cookie is set) and is redirected successfully to the second view, and after this close the browser/leaves the page (the cookie is still there). Another user write the url of the second view and because the cookie with the data of the first user is still there, he succeed to authenticate.
Can anyone help me with this?
plunker link
I am not sure. But maybe the browser closes before you even do anything in your logic.
Try this :
$scope.$on("$locationChangeStart", function(event){
event.preventDefault()
$cookieStore.remove('user');
});
try this code, it will works
$scope.$on('$locationChangeStart',function(event) {
if($scope.formsubmitted && $scope.myForm.$dirty){
var answer = confirm("Are you sure you want to leave this page?")
if (!answer) {
event.preventDefault();
}else{
$cookieStore.remove('user');
}
}
});

AngularJS (Restangular): Making a promise block? Need to use it for validating a token

I have stumbled upon Restangular for making calls to a rest service. It works great and returns a promise. I need to be able to have the call block. The reason for this is on a fresh page reload I am technically not loggged in but I may have a token stored in a cookie. i would like to validate this token against a rest service. Problem is that I need it to block.
If a timeout occurs or if its not valid that i can treat teh user as not authenticated.
This is the reason for wanting to block is that i would like to redirect them using $location.path to a new URL it not a valid token.
This doesn't happen on a specific route so i can't use resolve which is blocking. It technically happens on every route - I use the $on.$routeChangeStart and check an internal variable got LoggedIn or not, if not logged in i check for the stored token.
This happens on each Page refresh but not while navigating inside the application.
The affect I am trying to get is how Gmail works.
Look forward to any insight anyone has on this
Thanks
Basically you need to ensure that some asynchronous action occurs prior to any route change occurring, and in this case the action is authenticating a user.
What you can do is use the $routeChangeStart event that's emitted in order to add a property to the resolve object on the route like so:
function authenticate() {
if ( user.isAuthenticated ) {
return;
}
// Just fake it, but in a real app this might be an ajax call or something
return $timeout(function() {
user.isAuthenticated = true;
}, 3000);
}
$rootScope.$on( "$routeChangeStart", function( e, next ) {
console.log( "$routeChangeStart" );
next.resolve = angular.extend( next.resolve || {}, {
__authenticating__: authenticate
});
});
Since angular will wait for any promises in the resolve object to be fulfilled before proceeding, you can just use a pseudo dependency as in the example. Using something like that, you should be able to guarantee that your user is authenticating prior to any routes successfully executing.
Example: http://jsfiddle.net/hLddM/
I think the best way to do this might be to push the user around with $location.path, You can use .then() to effectively force a wait by leaving the user on a loading page.
var currentPath = $location.path();
$location.path(loadingScreen);
//Assuming you have some sort of login function for ease.
Restangular.login(token).then(
function(result) {
$location.path(currentPath)
},
function(error) {
$location.path(logInScreen)
}
);
If you're using ui-router, you could move to another state with the same URL, where you'd use that Restangular.login with the then, and in case of success go back to the "logged in" state, otherwise, go to the "log in" state where the user must enter his username and password.
If you're not using ui-router, you could implement something like that with some ng-switch.
So, upon arrival to the screen, you do that Restangular.login and by default you show loading page by setting some boolean to true. Then, if it doesn't succedd, you send him to the login, otherwise, you set loading to false and show page.
Anyway, I'd strongly recommend using ui-router, it rocks :)
Hope this works!

Resources