$state.go timing out within $stateChangeStart - angularjs

I'm currently trying to check to see if a user is authenticated when the state changes. In my run block I'm listening for $stateChangeStart event. In the following code I can currently get the console to log podio is authenticated and podio is not authenticated, but when i add $state.go into the catch statement, it loads infinitely and then times out. I've tried adding 'event.preventDefault` as it says here, but I don't fully understand why that would be necessary.
Run Block
$rootScope.$on('$stateChangeStart', function (event) {
...
Podio.podio.isAuthenticated()
.then(function () {
console.log('podio is authenticated');
})
.catch(function(error) {
console.log('podio is not authenticated');
event.preventDefault();
$state.go('login'); //when this is added it continues to load...
});
...
});
I'm sure it's not the state definitions, because they've been working correctly all along.
State Definitions
$stateProvider
// setup an abstract state for the tabs directive
.state('tab', {...})
.state('tab.events', {...})
.state('tab.event-detail',{...})
.state('tab.attendees',{...})
.state('tab.attendee-detail',{...})
.state('login', {
url: '/login',
templateUrl: 'views/login.html',
controller: 'LoginController'
});

You run isAuthenticated check on each stateChangeStart. If this check fails you end up in catch block navigating to some state. This triggers stateChangeStart again with failing authentication check.
You can add some flag to login state to allow access for unauthenticated users:
.state('login', {
url: '/login',
requireAuthentication: false,
templateUrl: 'views/login.html',
controller: 'LoginController'
})
And check it later:
$rootScope.$on('$stateChangeStart', function (event, toState) {
...
if(!toState.hasOwnProperty('requireAuthentication') || toState.requireAuthentication !== false)
Podio.podio.isAuthenticated()
...
}

Related

Angular states - resolve before exiting

My Angular app includes a main screen and several tabs and I'm using $stateProvider to switch between states. Whenever I'm leaving a tab (=state) I must perform an asynchronous operation. I would like to wait for that operation to complete before actually moving to the new tab. Somewhat like resolve that is done when leaving the state rather than entering.
How can this be achieved?
Example snippet:
$stateProvider
.state('myproject.main', {
url: '/main',
templateUrl: '...',
controller: 'MainCtrl'
})
.state('myproject.tabs.a', {
url: '/tab_a',
controller: 'TabACtrl',
templateUrl: '...',
onExit: doSave
})
.state('myproject.tabs.b', {
url: '/tab_b',
controller: 'TabBCtrl',
templateUrl: '...',
onExit: doSave
});
function doSave(MyService) {
return MyService.save(); //async, promise
}
Here I wanted to save (async) whenever switching between the tabs. However when entering the main state from a tab, I want to be sure that save has finished (doing the save on myproject.main's onEnter/resolve is not good for me).
Problem: when entering myproject.main (MainCtrl), the async MyService.save() operation is still processing. I want it to be completed/resolved.
I tried to hack it with the following piece of code, but it does not work - the state change enters a loop
.run(function($rootScope, $state, $stateParams, MyService){
$rootScope.$on('$stateChangeStart', function(ev, toState, toParams, fromState, fromParams){
if (toState.name === 'myproject.main' && //switching to main
fromState.name && //for a certain tab
fromParams.resume !== true ) {
ev.preventDefault(); //stop the state change
//do the save and then continue with the state change
MyService.save().then(function(){
$state.go(toState, {resume: true}) //continue with a "resume" parameter
});
}
});
});
Thanks!

loading a state depending on local storage token property - stateProvider, ionic / angular js

I have this code:
var app = angular.module('myApp', ['ionic']);
app.config(function($stateProvider) {
$stateProvider
.state('login', {
url: '/',
templateUrl: 'login.html',
controller: 'loginController'
})
.state('home', {
url: '/home',
templateUrl: 'home.html',
controller: 'homeController'
});
});
$urlRouterProvider.otherwise('/');
How can I add a condition to state provider to verify if localstorage.token exist. If yes go to home else go to login
Now, I am going all the time on login state and there (loginController) I verify if I have or not a token on localstorage. I'm not satisfied with my version... that's why I want to improve it
I would recommend setting home as your default route and performing a redirect if there is no token. So this would be your default route:
$urlRouterProvider.otherwise('/home');
And you would redirect if there is no token. You can do that by watching for the $locationChangeStart event in your run block:
.run(function ($rootScope, $state) {
$rootScope.on('$locationChangeStart', function(event, next, current) {
// check for the user's token and that we aren't going to the login view
if(!localStorage.token && next.templateUrl != 'login.html'){
// go to the login view
$state.go('login');
}
}
})
This has the added benefit of restricting the user to the login view if they have not authenticated.
In app.run you can add your logic; this will fire on page-load and on refresh.
.run(function ($state) {
if(localStorage.token) $state.go('home');
else $state.go('login');
})

Angular UI Router: nested states for home to differentiate logged in and logged out

I'm starting a new project using boilerplate MEAN provided by MEAN.JS (not .IO).
I'm new to ui-router and I'm having trouble figuring out how to accomplish this scenario:
if user is logged in, go to state "home.loggedIn".
if user is logged out, go to state "home.loggedOut"
the route url is "/" and shouldn't change.
here's how the route provider looks like currently:
angular.module('core').config(['$stateProvider', '$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
// Redirect to home view when route not found
$urlRouterProvider.otherwise('/');
// Home state routing
$stateProvider.
state('home', {
url: '/',
abstract: true
}).
state('home.loggedOut', {
templateUrl: 'modules/core/views/home.client.view.html'
}).
state('home.loggedIn', {
templateUrl: 'modules/core/views/dashboard.client.view.html'
});
}
]);
I'm looking for something like a pre-save hook in db terms to determine which state to go to. How would that look like?
There is a plunker providing behaviour as described above. Firstly, there is a change in a state definition, moving the url from abstract into both child states, and introducing logon state for later checks:
// Redirect to home view when route not found
$urlRouterProvider.otherwise('/');
// Home state routing
$stateProvider.
state('home', {
//url: '/',
abstract: true,
template: '<div ui-view=""></div>',
}).
state('home.loggedOut', {
url: '/',
...
}).
state('home.loggedIn', {
url: '/',
...
})
.state('logon', {
url: '/logon',
templateUrl: 'tpl.logon.html',
controller: 'LogonCtrl',
});
What we do have right now, is definition of 2 states, with a same url. The first will be taken as a default.. and used.
Now, we have to introduce a state change observer, which will redirect to proper sub-state, based on a AuthSvc setting isLoggedIn:
.run(['$rootScope', '$state', 'AuthSvc',
function($rootScope, $state, AuthSvc) {
$rootScope.$on('$stateChangeStart', function(event, toState, toParams
, fromState, fromParams) {
// logged out is logged out
var doRedirectToLoggedOut = !AuthSvc.isLoggedIn
&& toState.name === "home.loggedIn";
if (doRedirectToLoggedOut) {
event.preventDefault();
$state.go("home.loggedOut");
}
// logged in is logged in
var doRedirectToLoggedIn = AuthSvc.isLoggedIn
&& toState.name === "home.loggedOut";
if (doRedirectToLoggedIn) {
event.preventDefault();
$state.go("home.loggedIn");
}
});
}])
As this example shows in action, until we change isLoggedIn (click on logon link) we are redirected to correct sub-state ... even if we would like to see the other

AngularJS routing pulling main page content even when user not logged in

I am new to AngularJS. I made a simple app that have a login function using AngularJS. I used routing and on resolve i put some logic to check if user is logged in and then only proceed accordingly. I have everything working fine, the problem is, when i am not logged in, if i browse to /home it doesn't load the main.html page(that's how it's supposed to be) but a GET request gets called and that returns content of main.html in console.My code looks like this:
app.config(function($routeProvider){
$routeProvider.when('/', {
templateUrl: 'partials/login.html',
controller: 'LoginCtrl',
resolve:{
test: function($http, $q,$location){
var defer = $q.defer();
//checks if user is logged and returns boolean
$http.post('login/getLoggedUser', {}, {}).success(function(data){
if(!data.logged){
defer.resolve(data);
$location.url('/');
}
else{
defer.resolve(data);
$location.url('/home')
}
});
return defer.promise;
}
}
})
.when('/home',{
templateUrl: 'partials/main.html',
controller: 'MainCtrl',
resolve:{
test: function($http, $q,$location){
var defer = $q.defer();
$http.post('login/getLoggedUser', {}, {}).success(function(data){
if(data.logged){
defer.resolve(data);
$location.url('/home');
}
else{
defer.resolve(data);
$location.url('/')
}
});
return defer.promise;
}
},
})
.otherwise({ redirectTo: '/' });
});
When i direct to /home, GET http:/localhost:8080/an-grails/partials/main.html is called in console which contains the content of main page. How do i disable this call? Is there any other method to do this? I read documentation on AngularJS official page and also watched few videos of Egghead.io about resolve and got idea that controller and template gets loaded only after resolve is processed, So what am i doing wrong?
The simplest way to manage rights in your different routes is to catch the $routeChangeStart which is fired by the $route service everytime the route is changed.
With this, you can access the actual route and the next one. This object is the same that you register with $routeProvider.when(). You just have to add a boolean and compare this boolean with the actual user status.
$rootScope.$on("$routeChangeStart", function(event, next, current) {
if (next.loggedOnly && !user.isLogged()) {
// You should implement a isLogged method to test if the user is logged
$location.replace();
// This prevent a redirect loop when going back in the browser
return $location.path("/");
}
}
And inside your route declaration use :
$routeProvider.when('/home', {
templateUrl: 'partials/main.html',
controller: 'MainCtrl',
loggedOnly: true
});

Proper way to handle authentication

I have read a lot of different examples on how to implement authentication in an Angular app. I am using Firebase Simple Login as a (No-)Backend.
Users should only see any content of my app, once they are logged in. If they are logged out, all they can see is /auth/login and /auth/register. Currently, I have the following solution which works. But:
1) I am not sure whether this is bullet-proof because...
2) ... when I open /home manually while NOT logged in, I correctly get redirected to /auth/login but the HomeCtrl gets executed anyway. No sensitive data is exposed because no data is returned from the Backend, but there must be a way to NOT execute the controller at all if I am logged out?
3) Everytime I expose sensitive data, do I have to check inside my controller if the user is authenticated over and over again?
Bonus question: How do I redirect to /home after successful login? Currently, inside my AuthService I do $location.path('home'); on successful login, but this doesn't account for the state?!
My app.js:
angular.module('myApp', ['ionic', 'firebase', 'myApp.services', 'myApp.controllers'])
.run(function($rootScope, $location, AuthService) {
$rootScope.$on('$stateChangeStart', function (ev, to, toParams, from, fromParams) {
/**
* AuthService.isLoggedIn() returns TRUE or FALSE
* depending on whether user authenticated successfully
* against the Firebase backend
*/
// redirect to login
if (!AuthService.isLoggedIn() && to.name !== 'auth.register') {
$location.path('/auth/login');
}
});
})
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('auth', {
url: "/auth",
abstract: true,
templateUrl: "templates/auth-tabs.html",
controller: 'AuthCtrl'
})
.state('auth.login', {
url: '/login',
views: {
'login-tab': {
templateUrl: 'templates/auth-login.html'
}
}
})
.state('auth.register', {
url: '/register',
views: {
'register-tab': {
templateUrl: 'templates/auth-register.html'
}
}
})
.state('home', {
url: '/home',
templateUrl: 'templates/home.html',
controller: 'HomeCtrl'
});
$urlRouterProvider.otherwise('/home');
});
The way I've implemented it is by handling a 401 http server response, because I don't want to worry about checking for the user authentication state, I prefer letting the server handle that. That being said.
Documentation on $urlRouter.sync() specifies that if you prevent the default event, you may manually trigger the succesChange
angular.module('app', ['ui.router']);
.run(function($rootScope, $urlRouter) {
$rootScope.$on('$locationChangeSuccess', function(evt) {
// Halt state change from even starting
evt.preventDefault();
// Perform custom logic
var meetsRequirement = ...
// Continue with the update and state transition if logic allows
if (meetsRequirement) $urlRouter.sync();
});
});
$urlRouter documentation

Resources