I have simple angular ui router app:
.config(function($stateProvider, $urlRouterProvider){
$urlRouterProvider.otherwise('');
$stateProvider
.state('welcome', {
url: '/',
templateUrl: 'components/welcome/welcome.html',
controller: 'MainController',
data: {
auth: true
}
})
.state('courses', {
url: '/courses',
templateUrl: 'components/courses/courses.html',
controller: 'MainController',
data: {
auth: true
}
})
.state('login', {
url: '/login',
templateUrl: 'components/login/login.html',
controller: 'LoginController',
data: {
auth: false
}
})
})
And when user first time go to browser execute this code:
.run(['$rootScope','$http', '$state', 'UserService','userEmailGetService','isUserLoggedService',
function ( $rootScope,$http, $state, UserService,userEmailGetService,isUserLoggedService) {
UserService.CheckIfLogged()
.success(function(data) {
userEmailGetService.setUserEmail(data.email);
isUserLoggedService.setIsLogged(true);
stateCallback();
})
.error(function(data) {
//console.log(data);
isUserLoggedService.setIsLogged(false);
stateCallback();
$state.go("login");
});
this.stateCallback = function() {
$rootScope.$on("$stateChangeStart", function (event, toState, toParams, fromState) {
alert("Hello");
if (toState.data.auth == true && !isUserLoggedService.getIsLogged()) {
event.preventDefault();
$state.go("login");
}
if (toState.data.auth == false && isUserLoggedService.getIsLogged()) {
event.preventDefault();
$state.go("welcome");
}
});
};
}])
Routing in Firefox works ok but in Chrome when I logged and I go to login section, browser should ban this redirect and go to welcome section, when I run programmer console in chrome and I redirect to login panel when I logged evrythink works correct, very strange for me. When I added alert in stateChangeStart firefox show me this alert, chrome not, only when I open programmer panel.
You have a race condition as this is async:
UserService.CheckIfLogged()
.success(function(data) {
userEmailGetService.setUserEmail(data.email);
isUserLoggedService.setIsLogged(true);
stateCallback();
})
.error(function(data) {
//console.log(data);
isUserLoggedService.setIsLogged(false);
stateCallback();
$state.go("login");
});
This means that if the router is run before the validation code finishes, it will go to the login route instead of the welcome.
The solution is to use the resolve property of the routing, as it accepts promises, and will wait for the async response.
stateCallback is not defined because you assigned this function to this. Remove this from declaration this.stateCallback and then this code should work.
Related
I want to verify if the user can access a state before he gets there, if he doesn't have permissions will be redirected to another page.
The problem is that I'm doing a SPA and it verifies the permissions, but it takes a while until the server send the response and the user is redirected, so what happen is that a screen appears for 1 or 2 seconds and then is redirected successfully. Is there anyway to avoid this?
This is the code for the state change:
webApp.run(function ($rootScope, $state, StateService) {
$rootScope.$on('$stateChangeStart', function (event, toState, fromState, toParams) {
StateService.hasAccessTo(toState.name, function(data){
if (data.data != ""){
event.preventDefault();
$state.go(data.data);
}
});
});
});
and the service:
webApp.service('StateService', function($http, $rootScope){
this.hasAccessTo = function(state, callback){
$http.get("state/" + state).then(callback);
}
});
I have also tried with a promise in the $stateChangeStart, but it didn't work.
I read about interceptors, but they work if the user is in another page and access mine, if he is already on the page and type a link manually it doesn't intercepts.
Any modifications or suggestions of new ideas or improvements are welcome!
EDIT
Now I have this:
var hasAccessVerification = ['$q', 'StateService', function ($q, $state, StateService) {
var deferred = $q.defer();
StateService.hasAccessTo(this.name, function (data) {
if (data.data !== '') {
$state.go(data.data);
deferred.reject();
} else {
deferred.resolve();
}
});
return deferred.promise;
}];
$urlRouterProvider.otherwise("/");
$compileProvider.debugInfoEnabled(false);
$stateProvider
.state('welcome',{
url:"/",
views: {
'form-view': {
templateUrl: '/partials/form.html',
controller: 'Controller as ctrl'
},
'#': {
templateUrl: '/partials/welcome.html'
}
},
data: {
requireLogin: false
},
resolve: {
hasAccess: hasAccessVerification
}
})
And it validates, but it doesn't load the template. It doesn't show de views. What might I be doing wrong?
EDIT 2
I forgot to add $state here:
var hasAccessVerification = ['$q', '$state', 'StateService', function ($q, $state, StateService){...}
Consider using the resolve in your state configuration instead of using $stateChangeStart event.
According to the docs:
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.
Example:
var hasAccessFooFunction = ['$q', 'StateService', function ($q, StateService) {
var deferred = $q.defer();
StateService.hasAccessTo(this.name, function (data) {
if (data.data !== '') {
$state.go(data.data);
deferred.reject();
} else {
deferred.resolve();
}
});
return deferred.promise;
}];
$stateProvider
.state('dashboard', {
url: '/dashboard',
templateUrl: 'views/dashboard.html',
resolve: {
hasAccessFoo: hasAccessFooFunction
}
})
.state('user', {
abstract: true,
url: '/user',
resolve: {
hasAccessFoo: hasAccessFooFunction
},
template: '<ui-view/>'
})
.state('user.create', {
url: '/create',
templateUrl: 'views/user/create.html'
})
.state('user.list', {
url: '/list',
templateUrl: 'views/user/list.html'
})
.state('user.edit', {
url: '/:id',
templateUrl: 'views/user/edit.html'
})
.state('visitors', {
url: '/gram-panchayat',
resolve: {
hasAccessFoo: hasAccessFooFunction
},
templateUrl: 'views/visitor/list.html'
});
And according to the docs https://github.com/angular-ui/ui-router/wiki/Nested-States-%26-Nested-Views#inherited-resolved-dependencies resolve are inherited:
New in version 0.2.0
Child states will inherit resolved dependencies from parent state(s),
which they can overwrite. You can then inject resolved dependencies
into the controllers and resolve functions of child states.
But, please note:
The resolve keyword MUST be on the state not the views (in case you
use multiple views).
The best practice is to have interceptor on responseError which checks the response status and acts accordingly:
webApp.config(['$httpProvider' ($httpProvider) {
var interceptor = ['$q', '$rootScope', function ($q, $rootScope) {
return {
request: function (config) {
// can also do something here
// for example, add token header
return config;
},
'responseError': function (rejection) {
if (rejection.status == 401 && rejection.config.url !== '/url/to/login') {
// If we're not on the login page
$rootScope.$broadcast('auth:loginRequired');
}
}
return $q.reject(rejection);
}
}
}];
$httpProvider.interceptors.push(interceptor);
}]);
And handle redirection in run block
webApp.run(['$rootScope', function($rootScope){
$rootScope.$on('auth:loginRequired', function () {
$state.go('loginState');
});
}]);
The good thing is that $state service does not need to deal with permission logic:
$stateProvider
.state('someState', {
url: '/some-state',
templateUrl: '/some-state.html',
resolve: {
dataFromBackend: ['dataService', function (postingService) {
// if the request fails, the user gets redirected
return dataService.getData();
}],
},
controller: function ($scope, dataFromBackend) {
}
})
Notice
With this approach, you do not need StateService, all you need to do is to return proper response statuses from backend. For example, if the user is guest, return 401 status.
I am trying to make states and load views with UI router, except it keeps redirecting me to index. I am not able to figure out what is happening.
I have declared the app.js somewhere else, I am just posting the router.js here.
Code
angular.module("MyApp").config(function($stateProvider) {
$stateProvider
.state('clinicdashboard', {
url: '/clinicdashboards',
templateUrl: 'clinicdashboard/views/index.html',
controller: 'clinicDashboardCtrl',
resolve: {
loginRequired: loginRequired,
patientlist: ['$http', function($http) {
console.log("hello");
return $http({
method: 'GET',
url: 'users/api/getPatientList'
});
}]
}
})
.state('patientList', {
url: '/patientlist/:id',
templateUrl: 'clinicdashboard/views/detail.html',
controller: 'clinicDashboardCtrlDetail',
resolve: {
loginRequired: loginRequired
}
})
.state('clinicProfile', {
url: '/clinicProfile',
templateUrl: 'clinicdashboard/views/clinicProfile.html',
// controller: 'clinicDashboardCtrlDetail',
resolve: {
loginRequired: loginRequired
}
})
.state('clinicProfile.clinicProfileEdit', {
url: '/clinicProfileEdit',
templateUrl: 'clinicdashboard/views/clinicProfileEdit.html',
// controller: 'clinicDashboardCtrlDetail',
resolve: {
loginRequired: loginRequired
}
})
function loginRequired($q, $location, $auth) {
var deferred = $q.defer();
if ($auth.isAuthenticated()) {
deferred.resolve();
} else {
$location.path('/login');
}
return deferred.promise;
}
})
I have put all the files in their correct directory.
It is showing no error on the console or nodemon or anywhere.
Its simply just redirecting me to localhost, even if I try to go to something like localhost/clinicProfile
Can you try in your config,
yourapp.config(
function( $stateProvider,
$locationProvider) {
$locationProvider.html5Mode(false);
})
By default it is false, so you can remove this settings if you have specified and try.
I just suspected this because in your url (localhost/clinicProfile) , there is no "#". Have you tried localhost/#/clinicProfile as well?
Also, please try enabling HTML5Mode and and include this line in the head of index.html: OR try $locationProvider.html5Mode({ enabled: true, requireBase: false }); So $locationProvider wont be dependent on base tag.
Hi I 'm developing an app with IONIC Framework and am developing the user validation and error have not let me go , I leave some details of logic I'm using:
app.js:
angular.module('skulApp', ['ionic', 'ionic-material', 'ionMdInput', 'ngCordova'])
.run(function($ionicPlatform, $rootScope, $state, AUTH_EVENTS, _Auth_Service, sqliteService){
$ionicPlatform.ready(function(){
if (window.cordova && window.cordova.plugins.Keyboard)
cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
if (window.StatusBar)
StatusBar.styleDefault();
sqliteService.preloadDataBase(true);
});
$rootScope.$on('$stateChangeStart', function(event, next, nextParams, fromState) {
if ('data' in next && 'authorizedRoles' in next.data) {
var authorizedRoles = next.data.authorizedRoles;
if (!_Auth_Service.isAuthorized(authorizedRoles)) {
event.preventDefault();
$state.go($state.current, nextParams, {reload: true});
$rootScope.$broadcast(AUTH_EVENTS.notAuthorized);
}
}
if (!_Auth_Service.isAuthenticated()) {
if (next.name !== 'login') {
event.preventDefault();
$state.go('login');
}
}
});
})
app.config.js:
.config(function($stateProvider, $urlRouterProvider, $ionicConfigProvider, USER_ROLES) {
$stateProvider
.state('main', {
url: '/',
abstract: true,
templateUrl: 'templates/main.html'
})
.state('login', {
url: '/login',
templateUrl: 'templates/login.html',
controller: 'LoginCtrl'
})
.state('main.dashboard', {
url: 'main/dashboard',
views: {
'menuContent': {
templateUrl: 'templates/dashboard.html',
controller: 'DashboardCtrl'
},
'fabContent': {
template: '<button id="fab-profile" class="button button-fab button-fab-bottom-right button-energized-900" ui-sref="main.edit"><i class="icon ion-edit"></i></button>',
controller: function($timeout) {
$timeout(function() {
document.getElementById('fab-profile').classList.toggle('on');
}, 800);
}
}
},
data: {
authorizedRoles: [
USER_ROLES.admin,
USER_ROLES.teacher,
USER_ROLES.father
]
}
})
.state('main.edit', {
url: 'main/edit',
views: {
'menuContent': {
templateUrl: 'templates/edit.html',
controller: 'EditCtrl'
},
'fabContent': {
template: ''
}
}
});
$urlRouterProvider.otherwise(function($injector, $location){
var $state = $injector.get("$state");
$state.go('main.dashboard');
});
});
The error is:
Error: Cannot transition to abstract state '[object Object]'
at Object.transitionTo (ionic.bundle.js:40332)
at Object.go (ionic.bundle.js:40262)
at app.js:52
at Scope.$broadcast (ionic.bundle.js:23003)
at Object.transitionTo (ionic.bundle.js:40395)
at Object.go (ionic.bundle.js:40262)
at app.config.js:210
at check (ionic.bundle.js:39247)
at update (ionic.bundle.js:39259)
at Scope.$broadcast (ionic.bundle.js:23003)
Te line in app.js is that:
$state.go($state.current, nextParams, {reload: true});
Here I do not include services , constants, controls , policies, and others, if they consider it necessary I do know and update the question. Please i don't do! Help me
I think your problem is due to your $stateChangeStart method.
In case it enters the first couple of if conditions, this line:
$state.go($state.current, nextParams, {reload: true});
Will abourt your try to go to /main/dashboard, and the state will remain in /.
Well if you open your broswer on '/' the only matching state is your abstract one. Since it's matching otherwise doesn't get executed.
Use this instead of otherwise to redirect to your dashboard.
$urlRouterProvider.when('/', '/main/dashboard');
I'm building an app using sails.js backend and angular in frontend. I'm trying to prevent the user from accessing the admin control page if he's not authorized. I've run into couple of answers already, but none of them seem to fully work.
At the moment in my app.js, I have
$stateProvider
.state('home', {
url: "/home",
templateUrl: "home/homeTemplate.html",
controller: 'homeController'
})
.state('adminPage', {
url: "/adminPage",
templateUrl: "adminPage/adminTemplate.html",
controller: 'adminPageController',
resolve: {
validate: function($q, $sails, $location) {
var defer = $q.defer();
$sails.get("/user/getCurrentUser")
.success(function(response) {
if (response.user.accessAdminPage) {
defer.resolve();
}
else {
defer.reject("Access blocked");
$location.path('/');
}
return defer.promise;
})
}
}
})
The current code is partially working; The problem at the moment is, that when the unauthorized user first logs in and lands on the home page, and then accesses localhost:1337/#/adminPage, he actually reaches the page. The url in the address bar changes to localhost:1337/#/home but the user isn't redirected. Now the weird part is, when accessing the home page afterwards through the navbar and trying to access the admin page again, the user IS redirected to the home page as intended (although there's an annoying 'flash' while the page is reloaded).
For other people asking, this kind of handling has worked, and I'm wondering what I may have missed and generally any reasons for why my current solution isn't working.
You are returning promise from success function, this will never work.
You should return defered.promise (promise object) from outside success function.
CODE
$stateProvider
.state('home', {
url: "/home",
templateUrl: "home/homeTemplate.html",
controller: 'homeController'
})
.state('adminPage', {
url: "/adminPage",
templateUrl: "adminPage/adminTemplate.html",
controller: 'adminPageController',
resolve: {
validate: function($q, $sails, $location) {
var defer = $q.defer();
$sails.get("/user/getCurrentUser")
.success(function(response) {
if (response.user.accessAdminPage) {
defer.resolve();
} else {
defer.reject("Access blocked");
$location.path('/');
}
});
return defer.promise;
}
}
});
Hopefully this could help you, Thanks.
With the solution given by pankajparkar, the issue is that you will have to reply the logic in each state declaration. I recommend you to check the user's authorization in the onStateChangeStart event
angular.module('myApp', ['ui.router'])
.run(function($rootScope, AuthService){
$rootScope.$on('$stateChangeStart',
function(event, next, nextParams, prev, prevParams) {
AuthService.isNotAutorized()
.then(function() {
event.preventDefault();
$state.go('defaultState');
});
});
});
I am trying to set up angular routing. When I navigate to a route using <a ui-sref="classification.sequences.index"> it gets routed to the correct location,changes the url to http://localhost/classification/sequences/index and outputs enter 0, enter 5, enter 2 in the console.
But if I manually refresh the page or navigate to this url from scratch no routing happens and nothing is outputted to the console. But if I click the link button it will then route properly.
Is there something you have to set so angular will attempt to route on load or have I made a silly mistake somewhere?
My code can be seen below-
policyManagerApp.config(['$stateProvider', '$urlRouterProvider', '$locationProvider',
function($stateProvider, $urlRouterProvider, $locationProvider) {
// Activates HTML5 History API for modern browsers and sets the hashbang
// for legacy browsers
$locationProvider.html5Mode(true).hashPrefix('!');
$urlRouterProvider.when("", "/classification/index");
$urlRouterProvider.when("/", "/classification/index");
// For any unmatched url, send to /classification/index
$urlRouterProvider.otherwise("/classification/index");
$stateProvider
.state('classification', {
url: '/classification',
abstract: true,
template: '<ui-view/>',
onEnter: function () {
console.log("enter 0");
}
})
.state('classification.index', {
url: '/index',
templateUrl: '/html/partials/classification/index.html',
controller: 'PolicyManagerCtrl',
data: {
ncyBreadcrumbLabel: 'Classification'
},
onEnter: function () {
console.log("enter 1");
}
})
.state('classification.sequences', {
url: '/sequences',
abstract: true,
template: '<ui-view/>',
onEnter: function () {
console.log("enter 5");
}
})
.state('classification.sequences.index', {
url: '/index',
templateUrl: '/html/partials/classification/sequences/index.html',
data: {
ncyBreadcrumbLabel: 'Collection Sequences'
},
onEnter: function () {
console.log("enter 2");
}
})
.state('classification.sequences.detail', {
url: '/:id',
templateUrl: '/html/partials/classification/sequences/detail.html',
controller: 'SequenceDetailCtrl',
data: {
ncyBreadcrumbParent: 'classification.sequences.index',
ncyBreadcrumbLabel: '{{sequence.name}}'
},
onEnter: function () {
console.log("enter 3");
}
})
;
}
]);
It turns out I wasn't calling policyManagerApp.run() after the config line so it was never evaluating against the starting url.