Using $state methods with $stateChangeStart toState and fromState in Angular ui-router - angularjs

I'm writing a handler for $stateChangeStart:
var stateChangeStartHandler = function(e, toState, toParams, fromState, fromParams) {
if (toState.includes('internal') && !$cookies.MySession) {
e.preventDefault();
// Some login stuff.
}
};
$rootScope.$on('$stateChangeStart', stateChangeStartHandler);
toState does not have the includes method. Should I be doing something different, or is there a way to do what I'm trying to do?
Also, when //some login stuff includes a $state.go(...), I get an infinite loop. What might cause that?
Here's a more complete example demonstrating what we eventually got to work:
angular.module('test', ['ui.router', 'ngCookies'])
.config(['$stateProvider', '$cookiesProvider', function($stateProvider, $cookiesProvider) {
$stateProvider
.state('public', {
abstract: true
})
.state('public.login', {
url: '/login'
})
.state('tool', {
abstract: true
})
.state('tool.suggestions', {
url: '/suggestions'
});
}])
.run(['$state', '$cookies', '$rootScope', function($state, $cookies, $rootScope) {
$rootScope.$on('$stateChangeStart', function(e, toState, toParams, fromState, fromParams) {
if (toState.name.indexOf('tool') > -1 && !$cookies.Session) {
// If logged out and transitioning to a logged in page:
e.preventDefault();
$state.go('public.login');
} else if (toState.name.indexOf('public') > -1 && $cookies.Session) {
// If logged in and transitioning to a logged out page:
e.preventDefault();
$state.go('tool.suggestions');
};
});
});
I don't like using indexOf to search for a particular state in the toState. It feels naive. I'm not sure why toState and fromState couldn't be an instance of the $state service, or why the $state service couldn't accept a state configuration override in its methods.
The infinite looping was caused by a mistake on our part. I don't love this, so I'm still looking for better answers.

Suggestion 1
When you add an object to $stateProvider.state that object is then passed with the state. So you can add additional properties which you can read later on when needed.
Example route configuration
$stateProvider
.state('public', {
abstract: true,
module: 'public'
})
.state('public.login', {
url: '/login',
module: 'public'
})
.state('tool', {
abstract: true,
module: 'private'
})
.state('tool.suggestions', {
url: '/suggestions',
module: 'private'
});
The $stateChangeStart event gives you acces to the toState and fromState objects. These state objects will contain the configuration properties.
Example check for the custom module property
$rootScope.$on('$stateChangeStart', function(e, toState, toParams, fromState, fromParams) {
if (toState.module === 'private' && !$cookies.Session) {
// If logged out and transitioning to a logged in page:
e.preventDefault();
$state.go('public.login');
} else if (toState.module === 'public' && $cookies.Session) {
// If logged in and transitioning to a logged out page:
e.preventDefault();
$state.go('tool.suggestions');
};
});
I didn't change the logic of the cookies because I think that is out of scope for your question.
Suggestion 2
You can create a Helper to get you this to work more modular.
Value publicStates
myApp.value('publicStates', function(){
return {
module: 'public',
routes: [{
name: 'login',
config: {
url: '/login'
}
}]
};
});
Value privateStates
myApp.value('privateStates', function(){
return {
module: 'private',
routes: [{
name: 'suggestions',
config: {
url: '/suggestions'
}
}]
};
});
The Helper
myApp.provider('stateshelperConfig', function () {
this.config = {
// These are the properties we need to set
// $stateProvider: undefined
process: function (stateConfigs){
var module = stateConfigs.module;
$stateProvider = this.$stateProvider;
$stateProvider.state(module, {
abstract: true,
module: module
});
angular.forEach(stateConfigs, function (route){
route.config.module = module;
$stateProvider.state(module + route.name, route.config);
});
}
};
this.$get = function () {
return {
config: this.config
};
};
});
Now you can use the helper to add the state configuration to your state configuration.
myApp.config(['$stateProvider', '$urlRouterProvider',
'stateshelperConfigProvider', 'publicStates', 'privateStates',
function ($stateProvider, $urlRouterProvider, helper, publicStates, privateStates) {
helper.config.$stateProvider = $stateProvider;
helper.process(publicStates);
helper.process(privateStates);
}]);
This way you can abstract the repeated code, and come up with a more modular solution.
Note: the code above isn't tested

Related

Angular 1.x set initial state after page reload

Im using angular 1 for my app + ui router. I want the app to return to the initial state if page reloads. Currently, the app stays on the same page it was before, even after the browser refreshes. How can I make the app to return to login state after page reload? This is my app.js with the config data:
app.config(function($stateProvider, $urlRouterProvider) {
var loginState = {
name: 'login',
url: '/login',
templateUrl: 'templates/login/login.html',
controller: "loginController"
}
var homePageState = {
name: 'homePage',
url: '/homePage',
templateUrl: 'templates/chatRoom/homePage.html',
controller: "homePageController"
}
$stateProvider.state(loginState);
$stateProvider.state(homePageState);
$urlRouterProvider.otherwise('login');
}).run([
"$state",
function($state) {
$state.go('login');
}
]);
app.config(function($stateProvider, $urlRouterProvider) {
//...
}).run([
"$state",
"$rootScope",
function($state, $rootScope) {
$rootScope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState, fromParams) {
if (toState.name == fromState.name) {
$state.go('login');
}
});
}
]);
when refresh, the name of router is same. hope this helps you.
There is a simple javascript event which you can use.
<script>
window.onbeforeunload = function () {
window.location.href = 'http://localhost:61632/index.html#/login';
//Here set the path where you wanted your application to go after reload.
};
</script>
Just add this script tag to your index html file and you are done.

AngularJS controller middleware

I have an Angular endpoint like so:
$stateProvider.state("auth.signin", {
url: "/signin",
views: {
"auth-view#auth": {
templateUrl: "views/auth/signin.html",
controller: "SigninController"
}
}
});
I want a before filter (or a middleware) for my SigninController so if the user is already logged in, I want to redirect him to my HomeController.
In pseudo code, I'm looking for something like this:
$stateProvider.state("auth.signin", {
url: "/signin",
views: {
"auth-view#auth": {
templateUrl: "views/auth/signin.html",
controller: "SigninController"
}
},
before: () => {
if (User.loggedIn() === true) {
return $state.go("app.home");
}
}
});
I used similar features on alot of frameworks so I'm pretty certain Angular has something like this too. What's the Angular way of doing it?
Thank you.
Ps: I'm not using Angular 2 yet.
The ui router triggers a $stateChangeStart event, which you can capture:
app.run(["$rootScope", "$state", function($rootScope, $state) {
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams, options) {
if (toState.name === "auth.signin" && userIsLoggedInLogicHere) {
event.preventDefault(); // prevent routing to the state
$state.transitionTo("app.home");
}
// else do nothing, it will just transition to the given state
})
}]);
See this documentation for reference

Angularjs ui-router 404 not evaluating

The scenario is rather simple I have a local site that uses angular. I have my state provider set up us ui router lib.
If I type in http://localhost.com/#/lwejfpwef it will redirect me properly to my other wise clause to the state '/login'.
However I tried typing in http://localhost.com/lwjpoifwef it doesn't fire off any of the events that I have in my module config.
I have tried
$stateChangeSuccess
$stateChangeStart
$stateChangeError
$httpinterceptors
I'm not sure what I am doing wrong. It doesn't seem to hit any of the break points I put in there. Here is a snippet of what I have
angular.module('app').run(['$rootScope', '$state', 'service', function ($rootScope, $state, service) {
$rootScope.$on('$stateChangeSuccess', function (evt, to, params) {
alert('success');
});
$rootScope.$on('$stateChangeStart', function (event, to, params) {
alert('start');
});
$rootScope.$on('$stateChangeError', function (evt, toState, toParams, fromState, fromParams, error) {
$state.go('login');
});
}]);
angular.module('dealer-portal.core').config(CoreConfig);
function CoreConfig($stateProvider, $urlRouterProvider, $httpProvider) {
$stateProvider
.state('login', {
url: '/login',
templateUrl: 'wwwroot/app/login/login.html',
data: {
displayName: 'Login'
}
})
.state('home', {
url: '/home',
templateUrl: 'wwwroot/app/login/login.html',
data: {
displayName: 'Home'
}
})
.state('error', {
url: '/login'
});
$urlRouterProvider.otherwise('/login');
}
$state.go should have the state name and not the url passed in
$state.go('login');
and NOT
$state.go('/login');
Also, you need to inject $stateParams in your run function

UI-Router state changes but URL and template don't

I'm doing some onStateChange authorization like this:
angular
.module('app')
.run(['$rootScope', '$state', 'Auth', function ($rootScope, $state, Auth) {
$rootScope.$on("$stateChangeStart", function (event, toState, toParams, fromState, fromParams) {
console.log(toState);
if (!Auth.authorize(toState.data.allow)) {
console.log("Not authorized");
$state.go('app.auth.login', { redirect: toState.name });
}
console.log('Authorized');
});
}]);
Now, when I go to an unauthorized route, it acts as it should, up to a point. The authorization fails and it tries to redirect to app.auth.login. This is shown in the console.logs:
Object {url: "/", templateUrl: "modules/live/index.html", name: "app.live.index", data: Object}
app.routes.js:225 Not authorized
app.routes.js:222 Object {url: "/auth/login?redirect", templateUrl: "modules/auth/login.html", controller: "LoginController", name: "app.auth.login", data: Object}controller: "LoginController"data: Objectname: "app.auth.login"templateUrl: "modules/auth/login.html"url: "/auth/login?redirect"__proto__: Object
app.routes.js:229 Authorized
app.routes.js:229 Authorized
But the URL stays at the original state (the first attempted state), and I don't see the login screen. The LoginController never gets called (there's a console.log at the start that doesn't show.
If I visit /auth/login I see the login screen as intended.
Here's my app.routes.js:
/*****
* Auth
******/
.state('app.auth', {
abstract: true,
url: '',
template: '<div ui-view></div>',
data: {
allow: access.everyone
}
})
.state('app.auth.login', {
url: '/auth/login?redirect',
templateUrl: 'modules/auth/login.html',
controller: 'LoginController'
})
.state('app.auth.logout', {
url: '/auth/logout',
controller: 'LogoutController'
})
/*****
* Live
******/
.state('app.live', {
abstract: true,
url: '/live',
data: {
allow: access.live
}
})
.state('app.live.index', {
url: '/',
templateUrl: 'modules/live/index.html'
});
What can be causing this odd behaviour?
It happens in this cases, that we do forget to solve situation:
user is already (previously) redirected === user is going to $state.go('app.auth.login') ... no need to redirect
And we should not redirect in such case:
$rootScope.$on("$stateChangeStart",
function (event, toState, toParams, fromState, fromParams) {
if (toState.name === 'app.auth.login')
{
// get out of here, we are already redirected
return;
}
And once we do decide to redirect, we should stop current navigation with event.perventDefault();
if (!Auth.authorize(toState.data.allow)) {
// here
event.perventDefault();
$state.go('app.auth.login', { redirect: toState.name });
}
...
See more for example in this $stateChangeStart event continuing after state change or that Q & A with a working plunker

Divert to alternate homepage if user is not logged in using UI-Router & AngularJS

I would like to have two home pages, the first would be for users who have not logged in and the second for users that are logged in.
This is my current set up:
.config(function ($stateProvider, $urlRouterProvider, $locationProvider, $httpProvider) {
$urlRouterProvider
.otherwise('/');
$locationProvider.html5Mode(true);
$httpProvider.interceptors.push('authInterceptor');
})
.factory('authInterceptor', function ($rootScope, $q, $cookieStore, $location) {
return {
// Add authorization token to headers
request: function (config) {
config.headers = config.headers || {};
if ($cookieStore.get('token')) {
config.headers.Authorization = 'Bearer ' + $cookieStore.get('token');
}
return config;
},
// Intercept 401s and redirect you to login
responseError: function(response) {
if(response.status === 401) {
$location.path('/login');
// remove any stale tokens
$cookieStore.remove('token');
return $q.reject(response);
}
else {
return $q.reject(response);
}
}
};
})
.run(function ($rootScope, $location, Auth) {
// Redirect to login if route requires auth and you're not logged in
$rootScope.$on('$stateChangeStart', function (event, next) {
if (next.authenticate && !Auth.isLoggedIn()) {
$location.path('/login');
}
});
});
.config(function ($stateProvider) {
$stateProvider
.state('main', {
url: '/',
templateUrl: 'app/main/main.html',
controller: 'MainCtrl',
title: 'Home',
mainClass: 'home',
headerSearch: true
});
});
How could I reconfigure this so I could do something like the following:
.config(function ($stateProvider) {
$stateProvider
.state('welcome', {
url: '/',
templateUrl: 'app/welcome/welcome.html',
controller: 'WelcomeCtrl',
title: 'Welcome',
mainClass: 'welcome',
isLoggedIn: false
});
$stateProvider
.state('main', {
url: '/',
templateUrl: 'app/main/main.html',
controller: 'MainCtrl',
title: 'Home',
mainClass: 'home',
isLoggedIn: true
});
});
Just wanted to show, how we can manage authentication driven access to states. Based on this answer and its plunker, we can enrich each state (which should be accessible only for authenticated users) with a data setting, explained here: Attach Custom Data to State Objects (cite:)
You can attach custom data to the state object (we recommend using a data property to avoid conflicts)...
So let's have some states with public access:
// SEE no explicit data defined
.state('public',{
url : '/public',
template : '<div>public</div>',
})
// the log-on screen
.state('login',{
url : '/login',
templateUrl : 'tpl.login.html',
controller : 'UserCtrl',
})
...
And some with private access:
// DATA is used - saying I need authentication
.state('some',{
url : '/some',
template : '<div>some</div>',
data : {requiresLogin : true }, // HERE
})
.state('other',{
url : '/other',
template : '<div>other</div>',
data : {requiresLogin : true }, // HERE
})
And this could be hooked on on the state change:
.run(['$rootScope', '$state', 'User', function($rootScope, $state, User)
{
$rootScope.$on('$stateChangeStart'
, function(event, toState, toParams, fromState, fromParams) {
var isAuthenticationRequired = toState.data
&& toState.data.requiresLogin
&& !User.isLoggedIn
;
if(isAuthenticationRequired)
{
event.preventDefault();
$state.go('login');
}
});
}])
See all that in action here
There is similar Q & A were I try to show the concept of redirecting Authenticated and Not Authenticated user:
Angular UI Router: nested states for home to differentiate logged in and logged out
maybe that could help to get some idea, how we can use ui-router, and its event '$stateChangeStart' to hook on our decision manager - and its forced redirecting...
the code should be something like this
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState) { //, fromParams
console.log(toState.name + "," + fromState.name);
if(fromState.name === "") {
if (Auth.isLoggedIn()) {
$state.go('welcome');
event.preventDefault();
} else {
$state.go('home');
event.preventDefault();
} else {
if (toState.authenticate && !Auth.isLoggedIn()) {
$toState.go('login');
event.preventDefault();
}
}
}
so if user entering the application, then if he is logged in take him to welcome else take him to home.
once he is inside, then if he hits some route which needs auth.. then redirect him to login page..
sorry if i did not understood you requirement fully...
.config(function ($stateProvider,$rootScope) {
$stateProvider
.state('welcome', {
url: '/',
templateUrl: 'app/welcome/welcome.html',
controller: 'WelcomeCtrl',
onEnter: function() {
if (userIsLoggedIn()) {
$stateProvider.go('home');
}
});
});
I had problem like this and I solved it like this
.run(function ($rootScope, $location, AuthService) {
// start showing PRELOADER because we doing async call and we dont know when it will be resolved/rej
AuthService.checkLoginStatus().then(
(resp) => {
// fire logged in user event
$rootScope.$broadcast('userLogged',resp);
$location.path(YourHomePageUrl);
},
(err)=> {
// Check that the user is not authorized redirect to login page
$location.path(loginURL);
}
}).finally(
()=> {
// finnaly Set a watch on the $routeChangeStart
/// Hide PRELOADER
$rootScope.$on('$routeChangeStart',
function (evt, next, curr) {
if (!AuthService.isLoggedIn()) {
$location.path(art7.API.loginURL);
}
});
}
)
});
and im not using interceptor for handling 401 not authorized errors, thats my solution

Resources