framework
framework copied to clipboard
Switching application root (setRoot()) breaks after the second run
Firstly, apologies for the long-winded explanation but I thought it makes sense to explain everything step by step to make it easier to replicate the issue.
Background
I am writing a website that requires users to log in to access protected content. As expected, the login page has a completely different layout to all the authenticated pages that contain elements like toolbar, sidebar, navigation, etc.
Due to the lack of documentation I've not been able to find a definitive guide on what's the preferred way to implement multiple layouts and have, as a result, settled on having to have different application roots that I switch depending on whether the user is authenticated or not (for the purpose of this example I'll call them Login
and App
).
Application flow
When the application is first launched the configure(aurelia: Aurelia)
method in main.ts
checks if the current user is authenticated and if not sets the root to Login
in the result of which the user is presented with the login form.
export function configure(aurelia: Aurelia) {
aurelia.start()
.then(() => {
let root = authService.hasIdentity() ? 'app' : 'login';
aurelia.setRoot(root);
});
}
When the user submits the form and successfully authenticates with the server, the application stores the authentication identity locally, sets the root to App
and redirects user to the welcome page:
this.authService
.login()
.then(() => {
this.aurelia.setRoot('app')
.then(() => {
this.router.navigate('/welcome');
});
});
After that the user can log out of the session, which clears the local identity, sets the root back to Login
and redirects user to /
:
this.authService
.logout()
.then(() => {
this.aurelia.setRoot('login')
.then(() => {
this.router.navigate('/');
});
});
At this point, if the user wants to log back in, the expected behaviour is that they click on the Login button, the root gets changed to App
and they get redirected to /welcome
, yet that is not the case as the promise returned by setRoot()
never resolves and as a result the user remains on the same page.
The user does, however, get redirected if he/she clicks on the Login button again right after the previous attempt and from that point on the login -> logout -> login -> logout -> ... flow works as expected - the roots change and users don't get "stuck" on the login page.
Test case repository
I've created a simple test application (based on the Typescript skeleton app) which you can find here: https://github.com/codeaid/skeleton-navigation
To make it easier to understand what I've done there, the application consists of the following core components:
- Authentication service - validates credentials with the server and allows storing and clearing authentication identities locally on the clients. The "validation" is actually only a sleep to simulate a request delay.
-
Authorization step - ensures that a local identity is present before navigating to pages that have
{ settings: { auth: true } }
configured on their routes. If no identity is present, it changes the application root tologin
and redirects to the login page. -
Router configuration service - invoked only once in the
main.ts
file to register all available routes and add the authorization step. - Login - The login root/page.
- Page1 and Page2 - Two pages that require authentication identity to be present to access them.
The installation is the same as always:
cd ./skeleton-typescript
npm install
jspm install
gulp watch
Using the test application
To replicate the issue I've described above simply gulp watch
the project, switch to your browser and navigate to /
. You will be presented with the login page containing a Login button and two buttons allowing you to navigate to /page1
and page2
.
Click the Login button after which your local identity will get set and you will be redirected to page1. After that happens, click on the Log out button and you will be redirected back to the login page.
At this point if you click on the Login button again nothing happens and you remain on the same page. If you click on the button again, though, you get redirected to /page1
and if you keep logging out and in it works as expected.
Potential culprit
I tried to debug the code to understand what is causing the issue but ended up in the CompositionEngine
and eventually gave up as I'm not even sure what's happening in there.
I placed a tonne of console.info
calls around the aurelia-templating.js
and got the impression that for some reason the promise returned from waitForCompositionComplete()
does not get resolved and as a result the swap()
function doesn't get triggered.
Here's the code I'm talking about, which could be the culprit (trimmed to only show the important bits):
CompositionEngine.prototype._createControllerAndSwap = function _createControllerAndSwap(context) {
function swap(controller) {
// ...
}
return this.createController(context).then(function (controller) {
if (context.compositionTransactionOwnershipToken) {
return context.compositionTransactionOwnershipToken.waitForCompositionComplete().then(function () {
// this does not seem to get resolved
return swap(controller);
});
}
// context.compositionTransactionOwnershipToken is not empty, hence this doesn't seem to fire
return swap(controller);
});
};
That's as much as I can tell, the rest is in your hands! :)
Hope that helps and yet again, sorry for the wall of text!
Any development on this issue? We've a similar behavior within our application: http://stackoverflow.com/questions/38120388/aurelia-switching-between-app-roots-with-different-route-configurations/
@awflierman Can you confirm that it also "gets stuck" for you?
I, as the author of the StackOverflow question that @awflierman links to, can confirm that it does "get stuck" for me.
@EisenbergEffect Yes I can confirm
Can anyone create a repro of this issue? I have an application that sets root multiple times and does not exhibit the problem. Something must be different.
@EisenbergEffect, will do, sorry for the delay.
In your app with multiple roots, does every, or at least several of the roots have defined routes as well? Because that's when our problems arise. An app with two roots, with only one of them defining routes works flawlessly.
Ah. That must be it. In the app I have only one of the two roots has routes. That's the key I need to investigate further. Thank you!
@EisenbergEffect, have you had any chance to investigate this any further?
I can also confirm this behaviour. Splitting an application into two areas with different layout, based on logged-in status, has been non-trivial (and is a non-negotiable requirement in our case). Everything else has been super easy to accomplish though :)
We're facing the same issue here. Anyhow the quickfix for us was really simple and already described here https://github.com/aurelia/framework/issues/519 . Just switch the router configuration to the activate method of your app
Any update on this? The quick fix method does not work in my case.
I also want to inform about progress on this one. Having two application roots with their own separate routing makes a lot of sense in real-life apps (e.g. login, registration, ... vs the plain application's pages).
The workaround mentioned by @suamikim in the linked ticket #519 works for now, but I'd rather use a correct solution.
I'm facing this issue, as well. Any updates? Thanks!
I got this working, check out this stackoverflow thread: http://stackoverflow.com/questions/36247052/aurelia-clear-route-history-when-switching-to-other-app-using-setroot/40170520#40170520
please give the convention of =>
Stale since 2016
Ping @davismj to investigate since this relates to routing.
As I wrote in #804 it is definitely coming from the router and that maybe there needs to be two different routers when changing the root element, to have a predictable behaviour.