How to know next state in ui-router - angularjs

I'm showing and hiding a modal when the user go to a specific state (/login), but I would keep the modal in background if the users goes to /register from /login.
If the user is in /login and then goes to /register, I would the login modal to stay opened, while if the user is in /login and then goes to a different page, I would the login modal to disappear.
Actually I set the Angular-ui-router $stateProvider in this way:
app.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
$stateProvider
.state('home', {
url: "/",
templateUrl: "templates/home.html",
controller: 'HomeCtrl'
})
.state('login', {
url: "/login",
onEnter: function ($stateParams, $state, Modal) {
// * modalCtrl for Login
if (window.loginModal) {
window.loginModal.remove();
delete window.loginModal;
Modal.fromTemplateUrl('templates/user/login.html', function (modal) {
window.loginModal = modal;
loginModal.show();
});
} else {
Modal.fromTemplateUrl('templates/user/login.html', function (modal) {
window.loginModal = modal;
loginModal.show();
});
}
},
onExit: function ($stateParams, $state) {
if ( window.loginModal && /* condition like "!nextState.is('register')" */ ) {
window.loginModal.hide();
}
}
})
.state('register', {
url: "/register",
onEnter: function ($stateParams, $state, Modal, SlideBoxDelegate) {
// * modalCtrl for Register
if (window.registerModal) {
window.registerModal.remove();
delete window.registerModal;
Modal.fromTemplateUrl('templates/user/register.html', function (modal) {
window.registerModal = modal;
SlideBoxDelegate.update();
registerModal.show();
});
} else {
Modal.fromTemplateUrl('templates/user/register.html', function (modal) {
window.registerModal = modal;
SlideBoxDelegate.update();
registerModal.show();
});
}
},
onExit: function ($stateParams, $state) {
if ( window.registerModal ) {
window.registerModal.hide();
}
}
})
.state('not_found', {
url: "/not_found",
templateUrl: 'templates/not_found.html',
controller: 'NotFoundCtrl'
})
$urlRouterProvider.otherwise("/not_found");
$locationProvider.html5Mode(true);
})
Is there a way to set the condition like "!nextState.is('register')"?
Thank you for reading :)

You can listen for $stateChangeStart and note the value of the to parameter some place that is accessible to your onExit function, i.e. an injectable value or service.
In the future, there will be an injectable service that will represent the transition itself, which you'll be able to inspect and manipulate for things like this.

The best trick I have found so far is to use the promise inside the $state service, in the onExit function :
onExit: function ($state) {
$state.transition.then(toState => {
if (toState.name === 'nameOfTheWantedNextState') {
$state.go($state.current, {}, {reload: true});
}
})
}
This avoids listening to the event and storing the name somewhere, it only uses whats available at the time of the transition. Quite neat !

Related

$routeProvider - modal of a single page with different URL

I have a list of items showed in a page. The idea is to click on an item, and open a modal showing the information of the selected item, and in the background the list remains.
When the modal opens, the URL should change. So the idea would be to have a specific URL for every item, so when you access the url, you should see the modal, with the item info and in the background the main page.
I'm using $routeProvider for routing.
Example:
I would like to have something similar to this
http://www.eltrecetv.com.ar/articulo/le-salio-del-alma-el-emotivo-recuerdo-de-la-china-suarez-para-su-papa-en-una-fecha_087466
.config(function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/main.html',
controller: 'MainCtrl'
})
.when('/meals', {
templateUrl: 'views/meals/meals.html',
controller: 'MealsCtrl'
})
.when('/meal/:mealId', {
templateUrl: 'views/meals/meal-profile.html',
controller: 'MealCtrl'
})
The /meal/:mealId should be shown in a modal, and the URL should change when the modal is opened.
I've searched a lot, but I couldn't find a suitable answer for this.
Thanks for the help!!
You can specify states you want to show as modal and when it is handled, return to state you want to. For example:-
app.config(function ($stateProvider) {
$stateProvider.state('tasks', {
url: '/tasks',
templateUrl: 'tasks.html',
controller: 'TasksCtrl'
url: "/tasks/:id",
onEnter: function($stateParams, $state, $modal) {
$modal.open({
templateUrl: "show.html",
resolve: {},
controller: function($scope, $state) {
$scope.ok = function () {
$scope.$close();
};
$scope.dismiss = function () {
$scope.$dismiss();
};
}
}).result.then(function (result) {
// $scope.$close
}, function (result) {
// $scope.$dismiss
}).finally(function () {
// finally
return $state.transitionTo("tasks");
});
}
});
});
Here is plunker code for it : code
I will edit my answer for routeProvider also.

Use (asynchronous) $http result to (synchronously) check permissions on routes

I'm trying to check permissions on routes in an AngularJS application. Routes are handled by angular-ui-router.
My routes and permissions-per-route are defined as this:
angular.module('my_app')
.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('root', {
abstract: true,
templateUrl: 'content.html',
resolve: {
user_info: function (UserService) {
return UserService.user_info();
}
}
})
.state('root.some_page', {
url: '/',
templateUrl: 'some_page.html',
controller: 'home',
data: {
roles: ['some_right', 'some_other_right]
}
});
}
I then check for permissions as this:
[...].run(function ($rootScope, $state, UserService) {
$rootScope.$on('$stateChangeStart',
function (event, toState) {
if (!!toState.data) {
var required_roles = toState.data.roles;
if (!UserService.has_permission_in(required_roles)) {
event.preventDefault();
$state.go('root.access_forbidden');
}
}
});
});
All good for now. My issue is resolving user permissions. There are provided by a backend and fetched using $http.get(...). Since the $http service returns a promise, it don't always have the result when the $stateChangeStart is event triggered.
It's "good" when the application is "warm", but if I land on a protected page, it fails (the data are not ready). I have no way to "wait" for the promise.
What would be a "good" way of doing access control on routes?
In your protected state's definition:
.state('root.some_page', {
url: '/',
templateUrl: 'some_page.html',
controller: 'home',
resolve: {
access: function($http, $state){ //Note: you can inject your own services/factories too
var requiredPermissions = ['some_right', 'some_other_right'];
return $http.get(...).then(function(response){
var permissions = response.data.permissions;
var granted = permissions.reduce((prev,curr) => requiredPermissions.indexOf(curr) > -1 || prev, false);
if(!granted)
$state.go('publicState');
return permissions;
});
}
}
})
Resolve waits for promises to settle before moving to a new state.
Better:
function checkPermissions(requiredPermissions){
return function ($http, $state){ //Note: you can inject your own services/factories too
return $http.get(...).then(function(response){
var permissions = response.data.permissions;
var granted = permissions.reduce((prev,curr) => requiredPermissions.indexOf(curr) > -1 || prev, false);
if(!granted)
$state.go('publicState');
return permissions;
});
}
}
//In state definition
.state('root.some_page', {
url: '/',
templateUrl: 'some_page.html',
controller: 'home',
resolve: {
access: checkPermissions(['first','second'])
}
})

How to skip login page if user is already logged in ionic framework

I am working on an IONIC application where I have checked if user is already logged in and if user is already logged then application should redirect on dashboard. This functionality is working well, but the application first showing login page for couple of seconds and then redirect to the dashboard.
app.js
$rootScope.$on("$locationChangeStart", function (event, next, current) {
var prefs = plugins.appPreferences;
prefs.fetch('iuserid').then(function (value) {
if (value != '') {
$state.go('app.dashboard');
}
});
.config(function ($stateProvider, $urlRouterProvider, $httpProvider) {
$stateProvider
.state('app', {
url: "/app",
abstract: true,
templateUrl: "templates/menu.html",
controller: 'AppCtrl'
})
.state('login', {
url: "/login",
templateUrl: "templates/login.html",
controller: 'LoginCtrl'
})
.state('app.dashboard', {
url: "/dashboard",
views: {
'menuContent': {
templateUrl: "templates/dashboard.html",
controller: 'DashboardCtrl'
}
}
})
;
// if none of the above states are matched, use this as the fallback
$urlRouterProvider.otherwise('/login');
});
});
I don't know where I am making mistake.
Edit: I am able to authenticate and redirect to the dashboard but my problem is the login page displayed for few (up to 2) seconds and then redirect to the dashboard and I am working on IONIC application
Second Edit
I found the problem but don't know the solution. Preference work greatly in $ionicPlatform.ready but do not work in $locationChangeStart. And I need preference in $locationChangeStart because it runs before $ionicPlatformReady. I desperately need the solution.
I do the following:
in the app.js
.state('login', {
url: "/login",
templateUrl : "templates/session/login.html",
controller : 'SessionCtrl'
})
.state('register', {
url: "/register",
templateUrl : "templates/session/register.html",
controller : 'SessionCtrl'
})
.state('app', {
url: "/app",
abstract: true,
templateUrl : "templates/menu.html",
controller : 'AppCtrl',
onEnter: function($state, Auth){
if(!Auth.isLoggedIn()){
$state.go('login');
}
}
})
.state('app.main', {
url: "/main",
views: {
'menuContent': {
templateUrl : "templates/main_menu.html",
controller : "MainMenuCtrl"
}
}
})
$urlRouterProvider.otherwise('/app/main');
Auth is a factory, it stores the auth session in localstorage.
angular.module('auth.services', [])
.factory('Auth', function () {
if (window.localStorage['session']) {
var _user = JSON.parse(window.localStorage['session']);
}
var setUser = function (session) {
_user = session;
window.localStorage['session'] = JSON.stringify(_user);
}
return {
setUser: setUser,
isLoggedIn: function () {
return _user ? true : false;
},
getUser: function () {
return _user;
},
logout: function () {
window.localStorage.removeItem("session");
window.localStorage.removeItem("list_dependents");
_user = null;
}
}
});
I think you should listen to the event $stateChangeStart on the $rootScope, you can listen to this event during runtime, like this:
angular.module("myapp.permission").run($rootScope){
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
var loggedIn = true;
if(loggedIn) {
$state.go('app.dashboard');
}
}
}
Guys try this elegant solution:
if the user is not logged in and login is required it redirect to login.
if the user try to get to login and he already logged in, prevent him from doing it.
mark every path as "requireAuth" and it will work.
place your code in app.js
.run(function ($rootScope, $location, authService) {
$rootScope.$on('$stateChangeStart', function (ev, to, toParams, from, fromParams) {
if (to.requireAuth && !authService.isAuthed()) {
$location.path("/login");
}
else if (to.name == 'login' && authService.isAuthed()) {
ev.preventDefault();
$location.path("/dashboard");
}
});
})
Remove
$urlRouterProvider.otherwise('/loginpage')
at the bottom of routes.js file.
Inside your "run" block and outside of $ionicPlatform.ready you can use this:
//Check if user loggedin and redirect to login page if not
$rootScope.$on('$stateChangeStart', function (event, toState) {
//Check only if you are not in login page
if(toState.name.indexOf('app') !== -1 ) {
// If user is not logged, will redirect to login page, eg:
var prefs = plugins.appPreferences;
prefs.fetch('iuserid').then(function (value) {
if (value != '') {
$state.go('login');
}
});
}
});
After you must change your:
$urlRouterProvider.otherwise('/login');
for this:
$urlRouterProvider.otherwise('/dashboard');
The idea is directly route to dashboard and if is not logged in, will redirect to login page.

Angular - $state.go not redirecting as expected when user is not logged in

I'm trying to redirect the user to the login page if the user is not logged in. The only use case that I can't get to work is if the user points the browser to a URL that requires authentication.
If the user goes directly to http://.../index.html#/dashboard (requires authentication), the browser will attempt to load that page.
If the user first goes to http://.../index.html#/login and then replaces 'login' with 'dashboard', the app will correctly not allow that and redirect to the login page.
When the user goes directly to 'dashboard', I can SEE that the code hits the redirection logic, but it seems to just ignore it.
WAT?
Here's my ui-router config and code:
angular.module('app', [
'ngRoute',
'ngCookies',
'ui.router',
'datatables',
'datatables.bootstrap',
'angularMoment'
])
.config(config)
.run(run);
config.$inject = ['$stateProvider', '$urlRouterProvider', '$httpProvider'];
function config($stateProvider, $urlRouterProvider, $httpProvider) {
$stateProvider
.state('login', {
url: '/login?ac',
controller: 'LoginController as vm',
templateUrl: 'app/login/login.view.html'
})
.state('content', {
url: '/',
abstract: true,
views: {
// the main template will be placed here (relatively named)
'#': {
templateUrl: 'app/content-template/container-with-nav.partial.html'
},
'navigation#content': {
templateUrl: 'app/common/views/master-navigation.partial.html'
}
}
})
.state('content.dashboard', {
url: 'dashboard',
views: {
'': {
templateUrl: 'app/dashboard/dashboard.partial.html'
},
'glance#content.dashboard': {
templateUrl: 'app/dashboard/dashboard-overview.partial.html',
controller: 'DashGlanceController as vm'
},
'cases#content.dashboard': {
templateUrl: 'app/dashboard/dashboard-cases.partial.html',
controller: 'DashCasesController as vm'
}
}
})
.state('content.casedetails', {
url: 'case/:caseId',
views: {
'': {
templateUrl: 'app/pages/cases/case.main.partial.html',
controller: 'CaseController as vm'
},
'casedetails#content.casedetails': {
templateUrl: 'app/pages/cases/case.details.partial.html'
},
'citation#content.casedetails': {
templateUrl: 'app/pages/cases/case.citation.partial.html'
},
'payout#content.casedetails': {
templateUrl: 'app/pages/cases/case.payout.partial.html'
},
'conversation#content.casedetails': {
templateUrl: 'app/pages/cases/case.conversation.partial.html'
},
'actionpending#content.casedetails': {
templateUrl: 'app/pages/cases/case.action-pending.partial.html'
}
}
})
;
$urlRouterProvider.otherwise('/login');
$httpProvider.defaults.withCredentials = true;
}
run.$inject = ['$state', '$rootScope', '$location', '$cookies'];
function run($state, $rootScope, $location, $cookies) {
// keep user logged in after page refresh
$rootScope.globals = ($cookies.get('globals')) ? JSON.parse($cookies.get('globals')) : null;
console.log('globals (should be set if cookie was present): ', $rootScope.globals);
if ($rootScope.globals) {
console.log('Found a cookie for the logged in user: ', $rootScope.globals.currentUser);
}
$rootScope.$on('$locationChangeStart', function (event, next, current) {
var restrictedPage = $.inArray($location.path(), ['/login', '/register']) === -1;
var isUserLoggedIn = ($rootScope.globals) && ($rootScope.globals.currentUser) ? true : false;
console.log('restricted page: ', restrictedPage, ', logged in: ', isUserLoggedIn);
// redirect to login page if not logged in and trying to access a restricted page
if (restrictedPage && !isUserLoggedIn) {
// This actually gets entered!
console.log('Page is restricted and user is not logged in - redirecting to login from listener.');
$state.go('login', {ac: ''});
//$location.path('/login');
}
});
}
It will be because the config is redirecting the user to the new state before your run function has ran when going directly to the restricted state.
The reason is works when they go to login first is because you have already set your event listener and it doesn't get unbound when moving to the new state.
Basically, if you change the listener to the end of state change rather than the start event, it probably will work as the state change will not have ended.
$rootScope.$on('$locationChangeSuccess', function (event, next, current) {

Redirect state in angularjs

This is the state configuration:
angular
.module('grabhutApp', [...])
.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
// ACCOUNT
.state('account', {
abstract: true,
url: '/account',
templateUrl: 'index.html'
})
.state('account.main', {
url: '',
templateUrl: 'views/account/account.login.html',
controller: 'AccountController'
})
.
.
.
// NO VIEWS
.state('nv', {
abstract: true,
url: '/nv'
})
.state('nv.logout', {
url: '/logout'
})
});
The nv and its sub states will have no physical views or controllers.
I want them to serve as links that calls certain functions.
Service for calling logout methods:
angular.module('grabhutApp')
.factory('$grabhutAccountService', function ($state, $grabhutDataService) {
var methods = {
.
.
logoutUser: function () {
$grabhutDataService.user.removeSession();
$state.go('account.main', {}, {location: 'replace'});
}
.
.
};
return methods;
});
Then a button/link for logout:
<a ui-sref="nv.logout" class="button icon icon ion-log-out button-large" menu-close></a>
What I want to happen is that, when state nv.logout was triggered the
$grabhutAccountService.logoutUser() must be called and must redirect to 'account.main'
Here is what I've done so far:
I tried to use resolve in nv.logout
.state('nv.logout', {
url: '/logout',
resolve: {
logout: function ($grabhutAccountService) {
$grabhutAccountService.logoutUser();
}
}
})
The service was called but state did not redirect. So I tried another way. I added a controller:
.state('nv.logout', {
url: '/logout',
resolve: {
logout: function ($grabhutAccountService) {
$grabhutAccountService.logoutUser();
}
},
controller: function ($scope, $state) {
$scope.$on('$stateChangeSuccess', function () {
$state.go('account.main');
});
}
})
But $stateChangeSuccess is not being fired.
So I tried to use the rootScope:
.run(function(...., $grabhutAccountService){
.
.
.
$rootScope.logout = function(){
$grabhutAccountService.logoutUser();
};
.
.
.
})
And the use it like this:
<a ng-click="$root.logout()" class="button icon icon ion-log-out button-large" menu-close></a>
This works fine. But I'm worrying since (AFAIK) rootScope loads more data which could cause slower operation.
Besides, whenever I need some kind of function like above, I would have to attach function in rootScope again.
And I think that's not a good approach. BTW, I'm building this in phonegap that's why memory usage is so important.
Ooooh you're so close. I rearranged some of your code and arrived at this:
app.run(function($rootScope, $grabhutAccountService) {
$rootScope.$on('$stateChangeSuccess', function (evt, toState) {
if (toState.name === 'nv.logout') {
$grabhutAccountService.logoutUser();
$state.go('account.main');
}
});
});
The next major version of UI-Router will have much improved hooks for doing this sort of thing.

Resources