Ionic ui-router onEnter check throw errors at 2nd time around - angularjs

So I have this very strange error: I want to check if a user is login when enter a state and redirect them back to SignIn page if they are not. So in my config I have:
.state('home', {
cache: false,
abstract: true,
url: "/home",
templateUrl: "app/home/home.html",
onEnter: function($state, MyFirebaseService) {
// check session
var userId = MyFirebaseService.LoginUserId();
if (!userId) {
$state.go('auth.signin')
};
}
})
So I type in http://localhost:8100/#/home/courses to go into courses page without login, everything work perfectly. User got redirect back to auth.signin view. But when I type in the address bar again http://localhost:8100/#/home/courses, it throw 4 errors:
TypeError: Cannot read property '#' of null
TypeError: Cannot read property 'auth-signin#auth' of null
TypeError: Cannot read property 'auth-signup#auth' of null
My signin and signup are in an abstract view call auth. Why is that and how to fix it?

I personnaly perform those actions of authenticatio ncontrol on .run( with event catcher :
.run([
'$rootScope','authService','$q',
function($rootScope, authService,q) {
$rootScope.$on('$stateChangeStart', function(event, next, current) {
// YOUR CONTROL & REDIRECT
});
}]);
If you add some authorisation role control for instance on ui-router, for instance:
.state('home', {
url: "/home",
templateUrl: "includes/pages/homePage.html",
resolve : {
authorizedRoles : function(){ return [ USER_ROLES['su'],
USER_ROLES['user'],
USER_ROLES['admin'],
USER_ROLES['skyadmin'],
USER_ROLES['skyuser'],
USER_ROLES['skysu']
] }
}
})
You can easily check into $stateChangeStart with var authorizedRoles = next.resolve.authorizedRoles(); and compare them with your user roles

Related

unable to reload same state on page refresh

I have created SPA with angular v1.5.6 and angular-ui-router v1.0.0alpha0; Here is my Working Plunker .
in this SPA I have 3 page : landing page i.e. "/", /Home and /About . Home page required login ; after login we save credentials in local Storage so that on refresh of page we do not lose the page. everything is working fine but now I have issue on /Home page
when I refresh the /Home page , it gives below error for the property which is defined in data attribute of .state and page do not load
angular.min.js:117 TypeError: Cannot read property 'authorizedRoles' of undefined
route is defined in below way ( only relevant code shown below)
$stateProvider
.state("home", {
url: '/home',
templateUrl: './home/home.html',
controller: 'HomeController',
requiresAuth: true,
data : { pageTitle : 'Home', authorizedRoles: [USER_ROLES.admin, USER_ROLES.editor] }
})
and using below code in *.config* which are the cause of error
$transitionsProvider.onBefore({to: function(state) { return state.requiresAuth; } },
function($transition$, $state, AuthService) {
console.log(arguments);
var from = $transition$.to().name;
console.log(from);
if(!AuthService.isAuthenticated()) {
return $state.go("login", {referral : from});
} else {
console.log('I m logged in');
var roles = $state.current.data.authorizedRoles;
if(AuthService.isAuthorized(roles)) {
console.log('Authorized');
}
}
});
note: write any credentials in that plunker when asking for login.
after page refresh $state.current goes blank ; how to handle this situtaions?
Have you tried with dev tool?
Put a breakpoint in app.js:76 and click Home, you will see that your current state is base state which does not have data property.
resolved with this help from github
var roles = $state.current.data.authorizedRoles;
will be
var roles = $transition$.to().data.authorizedRoles;

AngularJS navigation

I have a requirement that states:
Logged in user can view employee list, individual employee view etc. For administrator, left navigation menu provides list of different entities and administrator should be able to view list of all employees for selected entity.
But I need to navigate administrator to home page once the entity is changed in left navigation drop down. employee-config.js uses employeeListCtrl and home page has its own controller (defined in separate home-config.js)
How to navigate from
$stateProvider.state("employeeList",{
url: "/employee/list",
templateUrl: cfg.URL_ROOT + "/IC/employee-app/employee-list/employee-list.html",
data: {
listStateName: "homeMain" // This is not working
},
controller: "employeeListCtrl as vm"
});
TO
$stateProvider
.state("homeMain", {
url: "/home/main",
templateUrl: cfg.URL_ROOT + "/IC/home-app/home-main/home-main.html",
data: {
listStateName: "homeMain"
},
controller: "homeMainController as vm"
});
If you talking about just redirecting to other states you can do with simple
$location.url("here your state names like 'employeeList')
and if you are talking about authorization of the current users this you can do with $stateprovider's onstatechangestart function in .config i would share this code for reference.
Blockquote
$rootScope.$on("$routeChangeStart", function(event, next, current) {
$rootScope.onLoading(); var authorised = null;
if (next.access !== undefined && next.access.requiresLogin) {
var userLoggedIn = UserService.getCurrentUser();
authorization.getConditions(next.access,userLoggedIn);
} });
there you can find next.access with reference to routes
ROUTER.when('dashboard_path', '/dashboard', {
controller : 'DashboardCtrl',
templateUrl : CONFIG.prepareViewTemplateUrl('videos/index'),
access: {
requiresLogin: true,
requiredPermissions: ['admin', 'AccountManager'],
permissionType: 'AtLeastOne'
}
});
there access would store values and we can get it into routechange function and compare.
Please note there is authorization is service which check server side user session existance.
please let me know if it helps or needed more details.

How do you protect /handle authenticated routes with ui-router?

Just started using angular and I'm trying to learn as fast as I can. I'm relatively new to SPA's so please bear with me and feel free to tell me if what I want to do is not feasible. What I'm currently stuck on now, is how do I protect my routes when using the ui-router?
What do I want to do?
There are routes that I don't want non-logged in users to access.
For example, /home and /login are okay for anonymous users.
/dashboard should only be for those that are logged in.
I want it so if a user tries to access /dashboard in the future without being logged in, they are not able to.
What have I already tried?
I have tried using the angular-permission module found here: https://github.com/Narzerus/angular-permission
The problem is..I'm not quite sure how to use it (nor if I'm using it properly).
What is currently happening?
In my login controller, once a user submits their username and password it makes a /POST to my web-sever. Once it gets the result, (regardless of what it is for the moment) I've got it redirecting to /dashboard.
Right now nothing should be getting to the /dashboard because no permissions have been set, yet I am (incorrectly) allowed to see the dashboard. I can both (1) successfully be redirected to the dashboard without permission and (2) access /dashboard without permission.
What does my code look like right now?
controllers.js
var controllers = angular.module('controllers',[])
// Login Controller -- This handles the login page that the user can enter
// enter his username & password.
controllers.controller('loginController', function($scope, $state,$location, LoginService){
$scope.email = "";
$scope.password = ""
$scope.login = function(){
var data = ({email:"test", password: "ayylmao"})
LoginService.login(data).then(function(res){
console.log(res);
})
.catch(function(err){
console.log("ERROR!");
console.log(err);
$state.go('dashboard')
})
}
})
app.js
//Definition: The parent module
var myApp = angular.module('clipboardApp', ['services','controllers', 'permission','ui.router']);
//Code below taken from the angular-permission docs.
angular
.module('fooModule', ['permission', 'user'])
.run(function (PermissionStore, User) {
// Define anonymous permission)
PermissionStore
.definePermission('anonymous', function (stateParams) {
// If the returned value is *truthy* then the user has the permission, otherwise they don't.
//True indicates anonymous.
//Always returning true to indicate that it's anonymous
return true;
});
});
//This will be serving as the router.
myApp.config(function($stateProvider, $urlRouterProvider, $locationProvider) {
//By default go
$urlRouterProvider.otherwise('/home');
//Views are
$stateProvider
.state('home', {
url: '/home',
templateUrl: 'views/home.html',
})
.state('login', {
url: '/login',
templateUrl: 'views/login.html',
controller: 'loginController'
})
.state('dashboard', {
url: '/dashboard',
templateUrl: 'views/dashboard.html',
controller: 'dashboardController',
data: {
permissions: {
except: ['anonymous'],
redirectTo: 'login'
}
}
});
});
Here is a working example with secured routes. In this example any state start with app. will go via the auth interceptor. $transitions.onBefore hook can be use as follows to satisfy your requirement.
.run(($transitions, $state, $injector) => {
$transitions.onBefore({
to: 'app.**'
}, () => {
const $window = $injector.get('$window');
if (!$window.sessionStorage.getItem('user')) {
return $state.target('login', $state.transition.params());
}
return true
});
});
https://plnkr.co/edit/ZCN2hB34mMEmBJulyaAJ?p=info

Route changes but view doesn't ui-router

I'm trying to redirect unauthorized people when they first enter my website via a url;
eg: example.com/#/order should be redirected to example.com/#/auth, this includes when they first visit the webpage and also navigating inbetween states.
Currently I have an abstract parent state of /order and /auth which have resolves that check for authentication and redirect otherwise. I also have a watch on the $stateChangeStart event to do the same thing.
The code for when you initially load the page works correctly, it will redirect if you visit /order/restaurant without being logged in, however if I'm on the url /auth/login I can change my url to /order/resturant and it will redirect me successful but the view will not update. I will still be able to see the /order/resturant page but the resolve and page changes were hit. Why does this happen? I've attempted to use $rootScope.$apply() without success as well.
My code is as follows for the parent states:
// Authentication Urls
.state('auth', {
url: '/auth',
templateUrl: 'modules/auth/auth.html',
abstract: true
})
// Order Urls
.state('order', {
url: '/order',
templateUrl: 'modules/order/order.html',
abstract: true
})
and my code to watch the stateChange
.run(['$rootScope', '$location', 'Auth', function($rootScope, $state, Auth) {
$rootScope.$on('$stateChangeStart', function(event, toState) {
var stateName = toState.name
console.log('State start')
if (!stateName.match(/auth/) && !Auth.isLoggedIn) {
console.log('User is not visiting auth and isn\'t logged in, redirecting....')
$state.go('auth.login')
} else if (Auth.isLoggedIn && stateName.match(/auth/)) {
console.log('User is logged in and is on the auth page, redirecting....')
$state.go('order.resturant')
}
})
}])
Looking at the documentation here (http://github.com/angular-ui/ui-router/wiki#state-change-events) you should cancel the navigation by calling event.preventDefault() before performing your new transition.

AngularJS UI Router - change url without reloading state

Currently our project is using default $routeProvider, and I am using this "hack", to change url without reloading page:
services.service('$locationEx', ['$location', '$route', '$rootScope', function($location, $route, $rootScope) {
$location.skipReload = function () {
var lastRoute = $route.current;
var un = $rootScope.$on('$locationChangeSuccess', function () {
$route.current = lastRoute;
un();
});
return $location;
};
return $location;
}]);
and in controller
$locationEx.skipReload().path("/category/" + $scope.model.id).replace();
I am thinking of replacing routeProvider with ui-router for nesting routes, but cant find this in ui-router.
Is it possible - do the same with angular-ui-router?
Why do I need this?
Let me explain with an example :
Route for creating new category is /category/new
after clicking on SAVE I show success-alert and I want to change route /category/new to /caterogy/23 (23 - is id of new item stored in db)
Simply you can use $state.transitionTo instead of $state.go . $state.go calls $state.transitionTo internally but automatically sets options to { location: true, inherit: true, relative: $state.$current, notify: true } . You can call $state.transitionTo and set notify: false . For example:
$state.go('.detail', {id: newId})
can be replaced by
$state.transitionTo('.detail', {id: newId}, {
location: true,
inherit: true,
relative: $state.$current,
notify: false
})
Edit: As suggested by fracz it can simply be:
$state.go('.detail', {id: newId}, {notify: false})
Ok, solved :)
Angular UI Router has this new method, $urlRouterProvider.deferIntercept()
https://github.com/angular-ui/ui-router/issues/64
basically it comes down to this:
angular.module('myApp', [ui.router])
.config(['$urlRouterProvider', function ($urlRouterProvider) {
$urlRouterProvider.deferIntercept();
}])
// then define the interception
.run(['$rootScope', '$urlRouter', '$location', '$state', function ($rootScope, $urlRouter, $location, $state) {
$rootScope.$on('$locationChangeSuccess', function(e, newUrl, oldUrl) {
// Prevent $urlRouter's default handler from firing
e.preventDefault();
/**
* provide conditions on when to
* sync change in $location.path() with state reload.
* I use $location and $state as examples, but
* You can do any logic
* before syncing OR stop syncing all together.
*/
if ($state.current.name !== 'main.exampleState' || newUrl === 'http://some.url' || oldUrl !=='https://another.url') {
// your stuff
$urlRouter.sync();
} else {
// don't sync
}
});
// Configures $urlRouter's listener *after* your custom listener
$urlRouter.listen();
}]);
I think this method is currently only included in the master version of angular ui router, the one with optional parameters (which are nice too, btw). It needs to be cloned and built from source with
grunt build
The docs are accessible from the source as well, through
grunt ngdocs
(they get built into the /site directory) // more info in README.MD
There seems to be another way to do this, by dynamic parameters (which I haven't used).
Many credits to nateabele.
As a sidenote, here are optional parameters in Angular UI Router's $stateProvider, which I used in combination with the above:
angular.module('myApp').config(['$stateProvider', function ($stateProvider) {
$stateProvider
.state('main.doorsList', {
url: 'doors',
controller: DoorsListCtrl,
resolve: DoorsListCtrl.resolve,
templateUrl: '/modules/doors/doors-list.html'
})
.state('main.doorsSingle', {
url: 'doors/:doorsSingle/:doorsDetail',
params: {
// as of today, it was unclear how to define a required parameter (more below)
doorsSingle: {value: null},
doorsDetail: {value: null}
},
controller: DoorsSingleCtrl,
resolve: DoorsSingleCtrl.resolve,
templateUrl: '/modules/doors/doors-single.html'
});
}]);
what that does is it allows to resolve a state, even if one of the params is missing.
SEO is one purpose, readability another.
In the example above, I wanted doorsSingle to be a required parameter. It is not clear how to define those. It works ok with multiple optional parameters though, so not really a problem. The discussion is here https://github.com/angular-ui/ui-router/pull/1032#issuecomment-49196090
After spending a lot of time with this issue, Here is what I got working
$state.go('stateName',params,{
// prevent the events onStart and onSuccess from firing
notify:false,
// prevent reload of the current state
reload:false,
// replace the last record when changing the params so you don't hit the back button and get old params
location:'replace',
// inherit the current params on the url
inherit:true
});
Calling
$state.go($state.current, {myParam: newValue}, {notify: false});
will still reload the controller, meaning you will lose state data.
To avoid it, simply declare the parameter as dynamic:
$stateProvider.state({
name: 'myState',
url: '/my_state?myParam',
params: {
myParam: {
dynamic: true, // <----------
}
},
...
});
Then you don't even need the notify, just calling
$state.go($state.current, {myParam: newValue})
suffices. Neato!
From the documentation:
When dynamic is true, changes to the parameter value will
not cause the state to be entered/exited. The resolves will not be
re-fetched, nor will views be reloaded.
This can be useful to build
UI where the component updates itself when the param values change.
This setup solved following issues for me:
The training controller is not called twice when updating the url from .../ to .../123
The training controller is not getting invoked again when navigating to another state
State configuration
state('training', {
abstract: true,
url: '/training',
templateUrl: 'partials/training.html',
controller: 'TrainingController'
}).
state('training.edit', {
url: '/:trainingId'
}).
state('training.new', {
url: '/{trainingId}',
// Optional Parameter
params: {
trainingId: null
}
})
Invoking the states (from any other controller)
$scope.editTraining = function (training) {
$state.go('training.edit', { trainingId: training.id });
};
$scope.newTraining = function () {
$state.go('training.new', { });
};
Training Controller
var newTraining;
if (!!!$state.params.trainingId) {
// new
newTraining = // create new training ...
// Update the URL without reloading the controller
$state.go('training.edit',
{
trainingId : newTraining.id
},
{
location: 'replace', // update url and replace
inherit: false,
notify: false
});
} else {
// edit
// load existing training ...
}
If you need only change url but prevent change state:
Change location with (add .replace if you want to replace in history):
this.$location.path([Your path]).replace();
Prevent redirect to your state:
$transitions.onBefore({}, function($transition$) {
if ($transition$.$to().name === '[state name]') {
return false;
}
});
i did this but long ago in version: v0.2.10 of UI-router like something like this::
$stateProvider
.state(
'home', {
url: '/home',
views: {
'': {
templateUrl: Url.resolveTemplateUrl('shared/partial/main.html'),
controller: 'mainCtrl'
},
}
})
.state('home.login', {
url: '/login',
templateUrl: Url.resolveTemplateUrl('authentication/partial/login.html'),
controller: 'authenticationCtrl'
})
.state('home.logout', {
url: '/logout/:state',
controller: 'authenticationCtrl'
})
.state('home.reservationChart', {
url: '/reservations/?vw',
views: {
'': {
templateUrl: Url.resolveTemplateUrl('reservationChart/partial/reservationChartContainer.html'),
controller: 'reservationChartCtrl',
reloadOnSearch: false
},
'viewVoucher#home.reservationChart': {
templateUrl: Url.resolveTemplateUrl('voucher/partial/viewVoucherContainer.html'),
controller: 'viewVoucherCtrl',
reloadOnSearch: false
},
'addEditVoucher#home.reservationChart': {
templateUrl: Url.resolveTemplateUrl('voucher/partial/voucherContainer.html'),
controller: 'voucherCtrl',
reloadOnSearch: false
}
},
reloadOnSearch: false
})
Try something like this
$state.go($state.$current.name, {... $state.params, 'key': newValue}, {notify: false})
In Angular 2, the accepted answer from RezKesh translates to the following:
this.uiRouter.stateService.go(
"home.myRouteState",
{
"param1": this.myParam1,
"param2": this.myParam2
},
{ notify: false }
);
Assuming you have injected UIRouter into your component's constructor as follows:
constructor(
private uiRouter: UIRouter
) { }
I don't think you need ui-router at all for this. The documentation available for the $location service says in the first paragraph, "...changes to $location are reflected into the browser address bar." It continues on later to say, "What does it not do? It does not cause a full page reload when the browser URL is changed."
So, with that in mind, why not simply change the $location.path (as the method is both a getter and setter) with something like the following:
var newPath = IdFromService;
$location.path(newPath);
The documentation notes that the path should always begin with a forward slash, but this will add it if it's missing.

Resources