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

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.

Related

url/state changes not working after angular.copy called in a factory

I'm at my wits end on this one, so any help is greatly appreciated!
I've been making an angular/node/mysql app for the past few weeks and came across this issue. I have a database running mysql that stores a list of users and their information. I created an API to provide the front-end with access to user data for login and all that. The flow is as follows:
User enters new state where info on them needs to be preloaded:
.state('info', {
url: '/info',
templateUrl: 'views/info.html',
controller: 'InfoController',
resolve: {
postPromise: ['user', function(user) {
return user.loadInfo();
}]
The code is run in the user factory so that data is fetched before the page loads:
app.factory('user', ['$http', 'auth', function($http, auth) {
var user = {};
user.loadInfo = function() {
var userId = auth.currentUser();
return $http.get('/users/' + userId).success(function(data) {
angular.copy(data, user);
});
};
return user;
}]);
The page loads fine the first time. Then I navigate somewhere else via $state.go('home') or something, and when I try to return to the same page (/info) it sometimes doesn't work. What happens is I enter in the new url, and the browser basically ignores it and just shows the url of my current page.
Start at localhost:8080/#/home
Type in localhost:8080/#/info and go there
Works fine first time. Navigate to localhost:8080/#/home
Now at localhost:8080/#/home
Type in localhost:8080/#/info, and the browser ignores it, just displaying localhost:8080/#/home
I've tried commenting out angular.copy, and that seems to fix the issue, but I still need to copy the data over that I fetch from the server (which does successfully get transferred).
So my primary question is: Is there an alternative to angular.copy that I can use that will work here? I still find it weird that it is used in all the examples but is causing this issue here.
Secondary question: If angular.copy is the way to go, have any of you seen this issue and have a guess at what's causing it? Does something look off in what I'm doing?
Bonus Info: I don't know how quite to describe this, but sometimes if I wait long enough before going back to the problem url, it works... I notice that a call is made to the server right as I'm typing in the problem url (before even hitting enter) and when that happens, it seems work. It's intermittent though, and stops any $state.go() calls from working to that problem url if they are called through a button click.
Alright, I got the answer for this one and it's pretty dumb. "user" is an object inside of the factory and "loadInfo" is a function attached to user. When I did the angular.copy(), data from the server overwrote the loadInfo function so I couldn't use it again...
So yeah, that's solved.

Adding a controller to my mean.js app seems to automatically fire an api call?

Having trouble tracking this down. I am adding a workflow to user signup in a meanjs app, such that an admin has to invite a user in order for them to be allowed to signup.
For some reason, whenever the invitation form loads, an API call is attempted to /api/users/invitation which I did not (as far as I know) ask for, and it doesn't make sense to have one as there's no data it needs. I assume there's something being auto-wired for me somewhere, but since this 404 causes the page to fail, I need to kill it if I can.
Here's my controller:
(function () {
'use strict';
angular
.module('users.admin')
.controller('InvitationController', InvitationController);
InvitationController.$inject = ['$scope', '$state', '$window', 'Authentication'];
function InvitationController($scope, $state, $window, Authentication) {
var vm = this;
vm.invitation = {};
vm.sendMail = sendMail;
vm.authentication = Authentication;
function sendMail(isValid) {
if (!isValid) {
$scope.$broadcast('show-errors-check-validity', 'vm.invitationForm');
return false;
}
var invitation = vm.invitation;
// TODO: send to the server
console.log(invitation);
}
}
}());
Here's the stateprovider fragment that's relevant:
.state('admin.user-invite',{
url: '/users/invitation',
templateUrl: 'modules/users/client/views/admin/invite-user.client.view.html',
controller: 'InvitationController',
controllerAs: 'vm',
data: {
pageTitle: 'Invite a User'
}
})
Any idea where else to look? This is my first app using the MEAN.js framework, though I've used angular quite a bit in the past.
OK, took me an embarrassingly long time to realize this, but it turns out the URL matching was the actual problem at hand.
I should have included my full set of client-side routes in the question, as what was happening was that /users/invitation was actually the last route declared. Turns out, /users/:userId was matching against that, and so the router was interpreting invitations as a userId, and the matching route had a resolver function which was in turn trying to call my server-side users api.
Now the part that is still baking my noodle (though it's less important in that my current problem is solved) is why would the route matcher do that, but still render the template that I'd assigned to the /users/invitation route? The fact that it was doing that certainly made the debugging longer, as the matched route has a very different template assigned to it than my intended route.
Can you examine network requests in developer tools. It could be a request to '/users/invitation', but the '/api' part is getting prefixed on the server side code (prefixed to router??).

locationChangeStart and preventing route change

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 :)

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