-
Notifications
You must be signed in to change notification settings - Fork 3k
$state.transitionTo('myStateName') inside "$stateChangeStart" handler looks like doing successful transition but visually not! #178
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Try doing evt.preventDefault() before you call your intended transition. Cool that you're using $couchPotato with ui-router! :) |
Replacing the current transition inside the start event should probably work even without preventdefault, so I'll leave this open as a bug. |
Replacing $state.transitionTo('MyIntendedStateName'); with $location.path('MyIntendedStateUrl'); works fine. Even inserting evt.preventDefault() before $state.transitionTo('MyIntendedStateName') works fine. evt.preventDefault();
$state.transitionTo('MyIntendedStateName'); (stu-salsbury, thank you for the workaround, and yes, couchPotato - awesome) But using bare $state.transitionTo() function not working as expected. In other words - transition to expected StateName triggering without any issue, but in reality (visually) DOM did't redraw with new State content. |
I am having a similar problem but the symptoms are not so clearcut. Here are the examples I have tried and their effects (the code below code is CoffeeScript)
The code above has no effect. The state transition does not cause any redirect and the original, requested url is served regardless.
The code above has some effect. Note that clicking on any link whilst the user is not logged in should always redirect to login. This works fine for the first two or three clicks and the login page is served however subsequent clicks serve up the requested url even though the $location.path line is being correctly run.
The code above works as expected. Whenever the state changes, whilst the user is not logged in, then the login page is served correctly. |
@biofractal Have you tried |
@nateabele sorry I missed out the most important one :-)
The code above does not work as expected. The browser is incorrectly redirected to the originally requested url, rather than So the incorrect url is not redirected but the page remains blank. |
@biofractal Okay then, you're welcome to create a Plunkr that reproduces the issue, and post the link here, per the instructions for reporting an issue. |
I have tried to write a plunker but I am afraid I could not get it working with angular. However it does show a simplified version of the problem: http://plnkr.co/edit/uJ7Dht4vxPZ83KHQx8q4 To be clear what the issue is: The $state.transitionTo('my-state') is not showing the template specified by the 'my-state'. I can see the content is loading (debug output shows the template is fetched), I can even see that the browser flashes with what appears to be the correct content, but then it reverts to the original, pre-transition content. So, if I display the 'home' content and then I run the following code:
Then the login template will load, flash briefly and then the browser will revert to displaying the previous home content. I hope this helps. Jonny |
Having the same problem as @biofractal mentioned. For the moment I had to move that condition check (securityService.isAuthenticated()) into $stateChangeSuccess event handler. |
I tried and abandoned the $on approach and went to using the resolve closure instead. The reason: even tho you're capturing the state change event. Its too late to doing anything... the state change has started and the new page / controller have started loading... before my isAuthenticated AJAX check (in the $on handler) had finished. Causing nullpointers, page flashes and more. Its not all the pretty but, the only reliable solution I've found. something like... resolve: {
userSession: function(Session, $state) {
return Session.validate().then(function(session) {
if (!session) {
$state.go('login.page1');
return null;
}
return session;
});
}
.... From what I can tell, resolve is the only reliable why to do real work before a page loads. |
I've just encountered this same problem with the latest 0.2.8 version. I was using $state.go inside the $stateChangeStart event handler with notify:false, to avoid infinite loop. The state was successfully changed, but the view was not rendered (location was changed). The problem I guess is that $stateChangeSuccess isn't called if notify is false. After modifying the code : angular-ui-router.js: 1821 To remove the if, everything was working as expected. |
Might be a good idea... |
Same issue here! Changing the The workaround by now to me is move the authentication to a mix of $stateChangeStart and a resolved promise. Also, I'm using 0.2.10. Current stable (0.2.8) brakes the entire app with another crazy loop! (call stack overflow) |
Just wanted to add that I'm also having the same issue. There's all sorts of weird behaviors when I event.preventDefault() and then state.go('login'). I get an error about 10 $digest calls, because of the infinite loop I'm assuming. It seems to me the infinite loop only happens on my initial application load. I have an otherwise('/dashboard') and when I just visit example.com (html5mode) it hits an infinite loop. However if I visit example.com/dashboard the $stateChangeStart event is fired once, for Dashboard state, then again for the login state. |
I am having the same issue as well. In $stateChangeStart I do a event.preventDefault() and the $state.go('login', null, {notify:false}); |
From what I can tell this issue resolves aroud two problems:
I can see two solutions here, either set the $state.transition before calling $stateChangeStart (in which case the new transtionTo will work). |
@Illniyar I'll try out some things based on your research and post my findings back here. |
Here is what I ended up with. The notify flag disables broadcasting of stateChangeStart and stateChangeSuccess. stateChangeSuccess is required for the correct operation of the router. I got around this by manually broadcasting the stateChangeSuccess event. In the ui router source code, in state.js, we need to change the options.notify flag to allow us to choose if we want we want to broadcast, both, none or one of these events. Perhaps something like a 'resume' or option, which works after the event.preventDefault() is called. @nateabele What do you think? Here is a really simple proof of concept: gfreeau@5579fe3 edit: I also found this discussion at #618, the latter comments are discussing what I'm talking about here. i.e $state.transitionTo(toState.name, toParams, {resume: true}); instead of (in my workaround code below) $state.go(toState.name, toParams, {notify: false}).then(function() {
$rootScope.$broadcast('$stateChangeSuccess', toState, toParams, fromState, fromParams);
}); options.resume would fire startChangeSuccess but not stateChangeStart. Here is my current workaround for now. thanks to @homerjam and #1158. .run(function ($rootScope, $state, $q, Auth, AUTH_EVENTS) {
$rootScope.$on(AUTH_EVENTS.notAuthenticated, function(event) {
console.log('not authenticated');
// go to login state
});
$rootScope.$on(AUTH_EVENTS.notAuthorized, function(event) {
console.log('not authorized');
});
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
var stateData = toState.data || {};
// my login state will early return here
if (stateData.allowAnonymous) {
return;
}
event.preventDefault();
// checks the server if the user has a saved json web token
// returns a promise, cached after first check
Auth.check()
.catch(function() {
$rootScope.$broadcast(AUTH_EVENTS.notAuthenticated);
return $q.reject('not authenticated');
})
.then(
function() {
// user authenticated, check authorization
var requiredRole = stateData.role;
if (requiredRole && !Auth.isAuthorized(requiredRole)) {
$rootScope.$broadcast(AUTH_EVENTS.notAuthorized);
return $q.reject('not authorized');
}
}
)
.then(
function() {
// user authenticated and authorized, activate the state
// $state.go(toState.name, toParams, {resume: true});
// workaround
$state.go(toState.name, toParams, {notify: false}).then(function() {
// line 907 state.js
$rootScope.$broadcast('$stateChangeSuccess', toState, toParams, fromState, fromParams);
});
}
)
;
});
}) |
Are there any news on this? I also have to use the stateChangeStart event and call state.go() there doing a little computation and pass the return value as route parameter to the state.go() function. But I can not due to the above behavior. The ui is not rendered :-/ |
@gfreeau I am using your workaround and so far it works. Thank you very much! |
+1 |
@gfreeau +1 , thank you! |
It appears that the release of v0.2.11 has no impact on this issue. Can anyone confirm? thx |
@gfreeau I found an issue in your workaround solution: if you have an config: $urlRouterProvider.when('/A', '/A/B') and then it will go to infinite loop when visit '/A' . |
I have made a small demo at: http://plnkr.co/edit/lUkJx27HiaaNAJbF07n2 If you click 'A' link then you will see angular error: "10 $digest() iterations reached. Aborting!". |
As a followup to my solution. Since the initial event is prevented. $state.transitionTo no longer returns the original promise, it returns a rejection. This means you cannot do this in the code:
The problem I am trying to solve is an initial application load of dynamic data before proceeding (such as making sure the current user is authenticated). According the the issues for AngularJS, it's possible angular may get an application wide resolve, meaning this work-around would no longer be needed. For UI router, it could be possible to provide a promise to stateChangeStart event, so you can return your own promise once your custom logic is done. Right now, if you prevent the initial transition, UI router returns a rejection. |
+1 for being able to return a promise from a state change event handler. I worked around this problem by de-registering the handler, before calling transitionTo: var deRegisterInterceptor = $rootScope.$on('$stateChangeStart',
function interceptor(e,toState,toParams){
e.preventDefault();
deRegisterInterceptor();
// do stuff
$rootScope.$state.transitionTo(toState.name,toParams);
}
); |
Have same issue, but only in Chrome and Opera. All examples can't help to avoid this. Also i have the same problem just going through usual ui-sref link. http://stackoverflow.com/questions/27491617/chrome-opera-blink-painting-issue |
@mikehaas763 did you ever find a way to resolve your problem? I'm experience the exact same behavior. It only happens when html5mode is true. The workaround provided by @gfreeau doesn't seem to work for me. |
Hi all! any news? |
We need to manually broadcast 'stateChangeSuccess'. See angular-ui/ui-router#178 for more details.
Well workaround didn't work for me ao is there any news on fix |
I ended up replacing the |
Got same issue. My case was related to using the $urlRouterProvider.otherwise in combination with the preventDefault and $state.go in a $stateChangeStart interceptor. A workaround is found here: #600 |
$state.transitionTo('myStateName') inside "$stateChangeStart" handler looks like doing successful transition but visually not!
After so-called successful transition current state shows expected value, but in reality my browser do not transitioned to it. Visually it's did't changed. What I doing wrong?
The text was updated successfully, but these errors were encountered: