Ui-Router otherwise rule with ng-admin - angularjs

I have got a problem in an angular app, where I have integrated ng-admin to provide a simple admin panel. I have also integrated Authentication and want to point to a login state, when there is no current session.
Ng-admin itself sets ui-router's otherwise method to point directly to the dashboard.
Even when setting my own $urlRouterProvider.otherwise(...) I am not able to force ui-router to use my otherwise function.
I tried it like this:
$urlRouterProvider.otherwise(function ($injector) {
console.log('Hello World?!');
state.go('login');
});
But the console.log is never printed...
I would like to intercept the state transition to the dashboard, before any dashboard-code is run, and redirect the user to the login page first.
Any hints and ideas are greatly appreciated.

Thanks #user8175473 for pointing me into the right direction.
I have solved the problem using $on('$stateChangeStart) as $on('$stateChangeSuccess) is called after some state code is executed.
$rootScope.$on('$stateChangeStart', function(event, toState) {
if(toState.name !== 'login' && !service.isLoggedIn()) {
event.preventDefault();
$state.go('login');
}
});
Note that event.preventDefault() is needed to cancel the state transition completely.

Related

Can I reload app without refreshing view?

I am using UI Router in my application, and I am wondering if I can reload - or destroy services when user is logging out, without refreshing the view? So far, when user is logging out, backend logs him out, and he is redirected to the login view. However, all of the services are still working. Any ideas?
Thanks
//Controller where the logout button is called
.controller("ctrl", function(destroyService){
this.logout = function(){
//Probably an ajax request to logout from server.
$http.post("logoutUrl", userId)
.then(function(){
destroyService.destroyAll();
$state.go("loginPage");
});
};
})
.service("destroyService", ["service1", "service2", function(service1, service2){
this.destroyAll = function(){
service1.destroyAll();
service2.destroyAll();
};
}]);
UI Router provides with state transition events, so you can listen for the transition event, and when the transition happens to the login page, you can manually refresh your services. By refresh I mean you can bring them to initial state by initializing them to initial state.
You can keep a function like close() in your service which declares everything again. So you can call this function on logout to refresh your service.
refer this for example: Refresh factory/service instance

Redirecting user to home page on state change (infinite loop error)

I am using UI Router and Firebase. Users are redirected to a thank you page after a transaction is complete. After a certain period of time, the session ends, and the user is automatically redirected to the home page.
If a user attempts to navigate away from the thank you page before then (e.g., by clicking the back button), I'd like to automatically redirect the user to the home page. The goal is to then have an unauthenticated, clean app.
When I use the following code, I get a maximum stack size error:
.run(['$rootScope', 'Auth', '$state', function($rootScope, $location, Auth, $state) {
$rootScope.$on('$stateChangeStart',
function (event, toState, toParams, fromState) {
if (fromState.name === 'thankyou' && toState.name !== 'thankyou') {
event.preventDefault();
Auth.$unauth();
$state.go('home');
}
})
}])
Problem
The condition that wraps the $state.go line will unconditionally catch itself.
Example
We try to navigate away from the thank you page toward some unspecified location
The condition catches us. We're on the thank you page, and we're not going to the thank you page. Better send us to the home page.
Hey, here we are again, trying to navigate from the thank you page to the home page. What could go wrong?
Oh noes! The condition has caught us again. We're on the thank you page, but we're going to the home page. That isn't the thank you page, so we have to the home page...
And the cycle continues forever...
The Solution
Change the condition to this:
if (fromState.name === 'thankyou' && toState.name !== 'home')

How to redirect in Angular if translate cookie is not set?

I want to redirect the user to a language page and not allow him to the index page if he hasn't chosen a language yet. I'm using the Angular Translate module. This module has cookie usage built-in with the following function:
$translateProvider.useCookieStorage();
This works. Now I would like to know if this cookie is set, and if not, redirect the user to the language page. Here's my idea how to handle this.
.factory('cookiecheck', function(){
if($translateProvider.CookieStorage.get) return true;
return false;
}
.run(function($rootScope, $state){
if(cookiecheck
{$location.path('/language');}
else
{$location.path('/home');}
This doesn't work. How would I best approach this? What is the correct syntax to determine if a CookieStorage exists and redirect?
You have a few syntax errors in your posted code, the factory isn't necessary as you can inject the $translate service into your run block.
.run(function($rootScope, $state, $location, $translate) {
var store = $translate.storage();
// if store is empty?
if(angular.equals(store, {}))){
$location.path('/language');
}
else {
$location.path('/home');
}
});
See ng-translate docs for cookie store
Also, since you won't know if and when the cookie will be expired or removed i think it is best to watch the route for changes and do your check then.
To do that hook into the $stateChangeStart event from ui router in your run block
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams){
// check then redirect
});
ui router docs
see this post for watching route changes

ui-router StateChange event go through even with redirection

I am developping an OAuth Provider application, using AngularJS and ui-router.
For each state change, I do the following check:
If the user is already logged in:
1.1 If the user is not an admin -> redirect him to the callBackUrl
1.2 If the user is an admin, do nothing
If the user is not logged in:
2.1 If the user tries to access an admin page -> redirect him back to login
2.2 If not, do nothing
my ui-router run method is the following:
.run(function ($rootScope, $state, $auth, accountService, $window, $stateParams) {
$rootScope.$on('$stateChangeStart', function (e, toState, toParams, fromState, fromParams) {
accountService.getUser({ token: $auth.getToken() }).$promise
.then(function (response) {
if (response.isAdmin === false) {
e.preventDefault();
$window.location.href = $stateParams.callBackUrl;
return;
}
})
.catch(function (response) {
if (toState.name.split(".")[0] === 'admin') {
e.preventDefault();
$state.go('root.login');
}
});
});
});
Everything is OK except for the part where I redirectthe user to the callback URL using $window
$window.location.href = $stateParams.callBackUrl;
This redirection takes 2-3 seconds, and in the meantime my user can see the page he is trying to access on my application. I thought the use of preventDefault() would solve that but it doesn't. Do you know how I could hold the $statechangeevent so that the user is redirected directly to the callback URL?
Thanks
I would put it this way:
The above approach allows user everything, until he is not really proven to be UN-authenticated. Why that? Because the code is calling service and evaluating all the stuff once the data are received. Meanwhile - we trust the user.
So, I'd suggest to change the approach
NEVER trust the user. He/she must to do the best to prove he/she is the right one, to get somewhere ... (well, kind of that...)
I described one possible way (with working example) here:
Confusing $locationChangeSuccess and $stateChangeStart
Just a piece of code to cite
Th first part of the $rootScope.$on('$stateChangeStart', ...:
// if already authenticated...
var isAuthenticated = userService.isAuthenticated();
// any public action is allowed
var isPublicAction = angular.isObject(toState.data)
&& toState.data.isPublic === true;
// here - user has already proved that he is the one
// or the target is public (e.g. login page)
// let him go, get out of this check
if (isPublicAction || isAuthenticated) {
return;
}
The second part, user is not trusted, he requires access to private stuff
// now - stop everything
// NO navigation
// we have to be sure who user is, to continue
// stop state change
event.preventDefault();
// async load user
userService
.getAuthObject()
.then(function (user) {
var isAuthenticated = user.isAuthenticated === true;
if (isAuthenticated) {
// let's continue, use is allowed
$state.go(toState, toParams)
return;
}
// log on / sign in...
$state.go("login");
})
Check that, in action, here
I know this was asked a while ago, but here is a possible solution:
Ui-router actually provides a fantastic way to solve this. By using a "resolve" within your $stateProvider, you can check that the user is authenticated before the controller of that particular state is even instantiated. Here is what ui-router docs say about resolve:
Resolve
You can use resolve to provide your controller with content or data that is custom to the state. resolve is an optional map of dependencies which should be injected into the controller.
If any of these dependencies are promises, they will be resolved and converted to a value before the controller is instantiated and the $stateChangeSuccess event is fired.
https://github.com/angular-ui/ui-router/wiki - Section for resolve is almost half way down page
You can run accountService.getUser within the resolve to check for an authenticated user, and by doing so, would prevent someone who is not authed from seeing the view they are trying to route to.
The resolve is set up inside the $stateProvider, and may look something like this:
$stateProvider.state('myState', {
resolve: {
userAuth: function(accountService) {
return accountService.getUser();
}
}
)
If you notice in the above example, I set a property called userAuth within the resolve. This can now be injected into any controller or service and then you can check against it for authenticated users. Each state that needs to be a "protected" view can contain the resolve, and then the 2-3 second flash of the view won't occur, as the controller hasn't been instantiated, and the user is redirected to another state.

Equivalence of window.onbeforeunload in AngularJS for routes

Regular JavaScript has window.onbeforeunload. How do I emulate the same in AngularJS with HTML5 routes?
There is $beforeRouteChange, but it doesn't allow you to cancel the event as far as I can tell.
To clarify: window.onbeforeunload would work for navigating away from the page, but not for intra page navigation, e.g. going from one controller to another just through the HTML5 history API.
Add this :
$scope.$on('$destroy', function() {
window.onbeforeunload = undefined;
});
$scope.$on('$locationChangeStart', function(event, next, current) {
if(!confirm("Are you sure you want to leave this page?")) {
event.preventDefault();
}
});
There is a $locationChangeStart event which can be canceled.
Since it hasn't been documented yet, here is the unit test describing how it affects the $route service.
https://github.com/angular/angular.js/blob/v1.0.1/test/ng/routeSpec.js#L63
This doesn't provide a confirmation for the user like window.onbeforeunload does. You need to bring your own dialog service and restart the location change process to get the same effect (or you could just use the synchronous $window.confirm).
If you are using angular-ui-router you need to use $stateChangeStart instead of $locationChangeStart.
When using UI-Router version 1.0.0+, use $transitions.onStart instead of $stateChangeStart. See the transition hooks documentation.
The $stateChange* events are deprecated as of version 1.0.0 (source):
All state events, (i.e. $stateChange* and friends) are now deprecated
and disabled by default. Instead of listening for events, use the
Transition Hook API.
Example code to abort a transition (source):
$transitions.onStart({}, function(transition) {
if (transition.to().name === 'unreachable') {
return false;
}
}

Resources