I have the following code redirect/transition logic in my appConfig for re-pointing of invalid routes:
$urlRouterProvider.when('/:lang/profile/:keywords/:id', ['$state', '$stateParams', function ($state, $stateParams) {
$state.transitionTo('root.public-profile.summary', $stateParams, {
reload: true,
inherit: false,
notify: true
});
with Summary route defined as:
state('root.public-profile.summary', {
url: "/summary",
templateUrl: "/templates/public.profile.summary.tpl.html"
})
So when a user attempts to access '/:lang/profile/:keywords/:id' it should redirect/re-point them to /:lang/profile/:keywords/:id/summary. This works fine when a view had been loaded before, if a user attempts to remove the 'summary' portion from the Url, it does gets re-appended automatically as expected. However, if a user copies and pastes 'wrong' link directly in a new browser window, the $stateParams object is empty and thus transition can't happen.
Is that behavior by design and is there a way to fix that?
You are right Val,
This behaviour is by design.You should see everything in the form of state-diagrams and Whether the transition will happen or not depends on what state is getting activated.What I can see from your snippet is that here you are working with ui-router where states carries more significance then the actual urls.
Not sure what you are trying to achieve but these docs can be helpful to you.
https://github.com/angular-ui/ui-router/wiki
Please Comment back for any other concerns or vote up if you are convinced with this answer.
Related
I'm working on an Ionic App where i run in some trouble.
I'm new to AngularJS and Ionic.
The user needs to be logged in to use the app. To check this an apicall to my server is called on entry of the app. If the user is not logged in, the app will show a modal where the user can login.
But in the small second that the apicall is made you see the "inside" of the app (restricted pages).
To avoid this i want to place a screen where the app checks if the user is logged in. And if the user is logged in, the screen needs to send the user further in the app.
The problem in this is that the app doesn't send the user on. I replaced all the code with a simple a href (ui-sref) but that doesn't work either.
The funny, weird part is that if i run it in my browser it works. But on my mobile(android) is doesn't do anything.
I tried it with a function with $location.path but i doesn't send me anywhere. It does set the path.
I tried $apply, $digest, $window.location.href .... etc etc. none worked.
If i don't use the wait screen everything works fine.
wait.html
<a ui-sref='app.proposals({"favorite" : 0})'>Continue</a>
RouteProvider
matcher.config(function($stateProvider, $urlRouterProvider, $ionicConfigProvider)
{
$ionicConfigProvider.tabs.position("bottom");
$ionicConfigProvider.tabs.style("standard");
$stateProvider
.state('app', {
url: '/app',
abstract: true,
templateUrl: 'templates/menu.html',
controller: 'AppCtrl'
})
.state('app.proposals', {
url: '/proposals/:favorite',
views: {
'menuContent': {
templateUrl: 'templates/proposals.html',
controller: 'ProposalsCtrl'
}
}
})
.state('app.proposal', {
url: '/proposal/:title/:id/:url',
views: {
'menuContent' : {
templateUrl: 'templates/proposal.html',
controller: 'ProposalCtrl'
}
}
})
.state('wait',{
url: '/wait',
templateUrl: 'templates/wait.html',
controller: 'WaitCtrl'
});
// if none of the above states are matched, use this as the fallback
//$urlRouterProvider.otherwise('/app/proposals/0');
$urlRouterProvider.otherwise('/wait');
});
Alright, now, the best way to solve this would be use a better practice. Your application follows the standard Authentication Flow. Now, according to me, wouldn't it be better if you just animate the current view in context. This will serve two purposes
reduce the render-load time;
get rid of you problem.
According to me, the problem here is that when you make the API call, Ionic tries to get hold of a cached page. The best way to fix it to have a default base check; point to index.html, the IndexController checks is the user is logged in, if it is, use $state.go("STATE_NAME") to go to the logged in state, otherwise, go to a state where you login.
Trust me when I say this: not only will this be easier, it'll also be more maintainable once the application code really builds up.
Should you require any further clarifications and/or code samples, just comment here. I haven't included them as it's trivial and extrapolates the logic and the syntax you're already familiar with.
I just wondering does anybody know how to debug situation when you have blank page for UI-Router. (Of course, without any errors in console)
By doing console.log for router events(take from here) I found you that it went to correct state but after did not stop on breakpoint inside state controller that I specified in UI-Router state:
.state('invited-user', {
parent: 'authPublic',
url: '/users/invitation/accept?back_link&invitation_token',
templateUrl: 'auth/invited-form.html',
controller: 'InvitedUserController',
data: {
hidePageNavigation: true
}
})
so I guess that controller is a problem.
Already tried to mock it:
.state('invited-user', {
parent: 'authPublic',
url: '/users/invitation/accept?back_link&invitation_token',
templateUrl: 'auth/invited-form.html',
controller: function(){
debugger
},
data: {
hidePageNavigation: true
}
})
but without any result, unfortunately.
I look for solution for current issue and general approach how to debug such kind of situations in general.
So I came to conclusion that the logic of debug should be so:
console.log router events (code is here) to check router logic
check the template of current router state (mock it if needed)
check the controller of current state (mock with empty function if needed)
check all templates and controllers of parent states from current one, starting from nearest parent and going up.
Actually it would be nice to have exception from box to see exact problem, but unfortunately we have what we have.
I'm working on an app in which user first select an item then get tabs related to actions available for this item.
In order to keep thing simple (and also because I'm not sure how to do otherwise) I pass the object id in the href of the tab so you have /{object_id}/tab1
The tab menu is created by the following :
.state('check', {
abstract: true,
url: "/check/:eventId",
templateUrl: "templates/check_menu.html",
controller: 'checkMenuCtrl'
})
It works perfectly for the first item, but if I go back to the menu and select another object, the href links of the tab menu are still with the first id.
It looks like the $stateParams.object_id used to populate tabs href is never updated.
.controller('checkMenuCtrl', function($rootScope, $scope, $stateParams) {
$scope.eventid=$stateParams.eventId;
})
Any idea of how I could do that ?
Thank you by advance for your help !!
Looks like abstract controlers are never updated.
$stateParams are updated in services (factory) so it's the better solution I found so far !
I have a nested state structure like this:
$stateProvider
.state('main', {
url: '',
abstract: true,
templateUrl: ...
controller: mainController,
resolve: {
Resolve1: ...
Resolve2: ...
}
})
.state('main.state1', {
url: '^/state1/:id/',
templateUrl: ....
controller: state1Controller,
resolve: {
Resolve11: ...
Resolve22: ...
},
})
.state('main.state2', {
....
From what I am seeing, when you are at state main.state1 and you navigate to the same state with another id parameter, the template for main.state1 is rendered fine but the template for main (the parent) is rendered before the state changes (I guess it doesn't wait for the data resolution of main.state1). This results to the view being rendered with the wrong data (more specifically, the state parameters that I use for generating links are wrong).
My mainController has the $stateParams and $state injected to it and that's where I get the data from.
Did anyone notice this before? Is this by design or is it a bug?
Is there any way to update the parent view with the latest data?
However, I would expect ui-router to wait for all data resolutions until it starts rendering the views (even the parent). I don't know if I am missing something here, but this is my understanding of the problem so far...
UPDATE:
I see now that a function that is involved in the view rendering (with interpolation) gets called many times, first with the old values and then with the new values. But the final result that I see on screen is using the initial data that were used when I first entered the 'main.*' state. This is SO weird.
UPDATE 2:
I have found that my links where not updated ONLY when using ui-sref (note that I was using parameters in the state href, e.g. ui-sref="main.state1({id:{{getId()}}})"). When switched to ng-href everything worked as expected.
I have no idea why, but that fixed the problem.
The odd thing was that the ui-sref was evaluated, but the link was not updated.
Go figure...
UPDATE 3:
This solution in turn caused other problems. Then the first click reloaded the application... So this is not the best fix...
The "solution" was to use ng-click with $state.go(), instead of links.
This appears to be a bug in ui-router.
ui-router transitions "through" the parent state to the child. That means main will be triggered, then main.state1. I ran into this myself where I was getting duplicate listeners and HTML DOM elements - main is actually running AT THE SAME TIME as main.state1. This behavior is by design, and AFAIK your only option is to design around it.
Ok, so I have a state for the url "/securepage" which I need to check whenever a user tries to access it. So I read there is an onEnter function I can use. But I cannot seem to get hold on the scope nor a service from there. What am I doing wrong?
.state('securepage', {
url: "/securepage",
template: securepage.html,
onEnter: function(){
// if (!$scope.main.isAuthenticated) $state.go("/login");
// if (!myLoginService.currentUser()) $state.go("/login");
Current options as I see it are to use resolve and/or check the authentication in the controller. But wouldnt an auth check be better placed in onEnter?
I ran into a similar problem today. Spent a whole day and finally came up with a workable solution other than the ones already suggested here.
My main goal is to find easy and effective way to selectively secure certain specific webpages. The security check needs to be performed before the HTML or any of the relevant controllers are loaded or called. If the check fails, the page may be forwarded to elsewhere without any side effects from the other controllers.
I tried the other suggested approaches. Each one has their own set of problems:
Using OnEnter:
There is no way to block ui-router from continuing with the state transition while making an async call to do the security check.
Using $rootScope.$on('$stateChangeStart'):
The management of the states that rquire security check will become separated from the $stateProvider.state() definitions. Ideally, I would rather see everything about the definition of a state defined all in one place. Though this is not a showstopper, it is not ideal.
A much bigger problem is that the $stateChangeStart event is not being called for the initial loading of a page. This one is a showstopper.
My solution is to use a resolve function to define a promise that will cause the view controllers to wait for the deferred to complete before they are called. This work perfectly for blocking the controller from starting in an asynchronous fashion.
Here's a rough outline of the code I used:
.config(['$stateProvider', function ($stateProvider) {
// Handler for Restricting Access to a page using the state.resolve call
var accessRestrictionHandler = function($q, $rootScope, $state) {
var deferred = $q.defer();
// make sure user is logged in
asyncCheckForLogin(function(status) {
if (status != "Logged In") {
// You may save target page URL in cookie for use after login successful later
// To get the relative target URL, it is equal to ("#" + this.url).
// The "this" here is the current scope for the parent state structure of the resolve call.
$state.go("loginPage");
}
else // if logged in, continue to load the controllers. Controllers should not start till resolve() is called.
deferred.resolve();
}.bind(this));
return deferred.promise;
};
$stateProvider
.state('userProfile', {
url: '/userProfile',
views: {
'main': {
templateUrl: 'userProfile.html',
controller: 'userProfileCtrl'
}
},
// SIMPLY add the line below to all states that you want to secure
resolve: { loginRequired : accessRestrictionHandler }
})
.state(.... some other state)
.state(.... some other state);
}]);
I hope this will help some of you out there.
Another approach would be to have a service/controller listen to the "$stateChangeStart" event. There, you could check if the called state needs authentication and reroute the request. Here's a snippet:
$rootScope.$on('$stateChangeStart', function (event, nextState, currentState) {
if (!isAuthenticated(nextState)) {
console.debug('Could not change route! Not authenticated!');
$rootScope.$broadcast('$stateChangeError');
event.preventDefault();
$state.go('login');
}
});
isAuthenticated could hold the call to your services, check nextState.data for authentication-related properties, etc.
Check out this issue and this example from their github page. It should give you some clues.
A late answer but well I'd rather write it anyway. I prefer not touching the $rootScope wherever possible. Here's the code that I'm currently working on which sums up another solution for your question:
state('dash', {
url:'/',
views:{
"topNav":{
templateUrl:"user/dash/partials/top-nav.html",
controller:'logoutCtrl',
},
"sideNav":{
templateUrl:"user/dash/partials/side-nav.html"
},
"body":{
templateUrl:"user/dash/partials/body.html",
controller:'testCtrl'
}
},
onEnter: function(Auth, $state){
if(!AuthSvc.isAuthenticated){
$state.go('login');
}
}
})
I'm using JWTs to store the tokens on the localstorage by using ngStorage which gives a $localStorage service which I inject in the Auth factory I injected in onEnter
probably you mean
.state('securepage', {
url: "/securepage",
template: securepage.html,
//this is how you actually inject things to onEnter property
onEnter: ['$state','myLoginService',function($state,myLoginService){
if (!$scope.main.isAuthenticated) $state.go("/login");
if (!myLoginService.currentUser()) $state.go("/login");`}]
....
I am using angularjs 1.3 with ui.router 0.2.10
I'm sure a lot has changed since this question was asked but as I had to figure it out myself and my search led me here...
As far as checking authentication you could do it like this
.state('securepage', {
url: "/securepage",
template: securepage.html,
onEnter: function($scope,$state,myLoginService){ //THIS IS THE CHANGE
if (!$scope.main.isAuthenticated) $state.go("/login");
if (!myLoginService.currentUser()) $state.go("/login");
You can put pretty much provider/service/factory in the onEnter function to gain access to it, and this does work from inside the .config of the app.
Having said that, they (ui-router makers) suggests to use custom rule function handle it inside .$on('$stateChangeStart', function(e, to) https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions#how-to-create-rules-to-prevent-access-to-a-state
I don't know why they do this instead of onEnter, maybe someone could expand on that.