How to debug a blank page error (caused by UI-Router)? - angularjs

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.

Related

How to capture invalid URL and send it to default controller/view in Angularjs with ui-router

I have a few simple states set up as well as a default state for invalid URLs.
$stateProvider
.state('visitor', {
url: '/visitor',
templateUrl: '/visitor/register.html',
controller: 'RegisterController'
})
.state('member', {
url: '/member',
templateUrl: '/member/login.html',
controller: 'AuthController'
});
$urlRouterProvider.otherwise('/visitor');
In the case of an invalid URL, I'd like to be able to capture it and then pass it along to the default state's controller so that I can log it somewhere, display it to the user once they've been redirected to the default view, whatever....
Aside from the URL matching, is there any object through which that invalid URL would be exposed?
There is the $stateNotFound event that would be perfect if it fired in the case of invalid URLs being requested as well as states. Alas, this does not seem to be the case.
As i look at ui-router documentation there is an event
$stateNotFound that is raised when state is not found. Can you see if it is helpful. Here is what the ui-router help states:
$stateNotFound - v0.3.0 - fired when a requested state cannot be found
using the provided state name during transition. The event is
broadcast allowing any handlers a single chance to deal with the error
(usually by lazy-loading the unfound state).

UI-Router $stateParams empty on initial "invalid" route page load

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.

ui-router renders template before state changes

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.

# and named# doesn't work on the same state views with resolve

I have a state named 'index' that, depending on the ui-view on the page, display different information and have different templateUrl and share the same URL:
$stateProvider.state('index', {
url: '/',
views: {
'all#': {
templateUrl: '/templates/partials/all/index',
controller: function(){
console.log('main');
}
},
'#': {
templateUrl: '/templates/partials/home/index',
controller: Controllers.Index,
controllerAs: 'academia',
resolve: {
templates: Preload.go(),
academia: ['Info', (Academia: Academia.Services.Info) => {
return Academia.get();
}],
plans: ['Planner', (Planner: ngPlanner.Planner) => {
return Planner.init();
}]
}
}
}
});
when the page is loaded with a <div ui-view></div>, it works as intended (matching the '#' rule). but when the page is loaded with <div ui-view="all"></div> it doesn't.
A plunkr showing the problem http://plnkr.co/edit/a2SWbB?p=preview
The resolve from the unnamed # shouldn't interfere with the all# view. The current resolve is being rejected because it doesn't belong on the current view (since there's no unnamed view when the page is loaded). Is it a bug, or am I doing something wrong?
At these days, the UI-Router implementation is driven by state management. And despite of the fact, that there are some voices/requirements to split the resolve of the views (well, not the parent view vs child view - but that is discussed below) ... I would say, current behavior in the Q&A described above is simply correct.
Firstly, what is the problem in above question? In general there are two or more sibling views, with their own resolve definitions:
views: {
'view1': {
....
resolve : { // could fail
}
},
'view2': {
...
resolve: { // could fail as well
...
What is state?
A state is a cluster of settings, representing the state. (sounds like self proving statement, but I am serious). All the settings are about state definition, about one, specific state.
We can use more views to represent it (e.g. title and content).
We can use some resolve statements (for all sibling views, for some of them) to support these views.
We have still only ONE state.
And that's it. It is all or nothing. The state is one, one cluster of settings, which all of them must be workign/resolved - or the whole state is failing.
Another story: parent - child
There are also some requirements to let the parent view to render once all its stuff is resolved. Why? Currently, if we navigate directly to child state, all/both resolves must be resolved before the state is initiated, the views are rendered.
That could be really improved. Make sense. And as far as I remember it is planned to happen in later releases (I'would swear I've seen that in one of the comments by UI-Router team member - but cannot find it).
It is different scenario. Because 1) we can go to parent (list view) first. And then 2) select child (detail). In this scenario it could really happen that list is working, detail fails.
So going directly to child should render parent, next fail on a child... But this is different story then the sibling described above.
Suggestions:
My suggestion would be, keep in resolve only stuff which is essential, absolute MUST for a state. Move the rest into Controllers injected services. This is my suggestion and my way...

Access scope or service in ui router onEnter to check access (auth)

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.

Resources