framework icon indicating copy to clipboard operation
framework copied to clipboard

Switching application root (setRoot()) breaks after the second run

Open codeaid opened this issue 8 years ago • 18 comments

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 to login 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!

codeaid avatar Apr 14 '16 19:04 codeaid

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/

andrewflierman avatar Jun 30 '16 12:06 andrewflierman

@awflierman Can you confirm that it also "gets stuck" for you?

EisenbergEffect avatar Jul 06 '16 01:07 EisenbergEffect

I, as the author of the StackOverflow question that @awflierman links to, can confirm that it does "get stuck" for me.

spapaseit avatar Jul 08 '16 10:07 spapaseit

@EisenbergEffect Yes I can confirm

andrewflierman avatar Jul 12 '16 11:07 andrewflierman

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 avatar Jul 12 '16 14:07 EisenbergEffect

@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.

spapaseit avatar Jul 15 '16 07:07 spapaseit

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 avatar Jul 15 '16 12:07 EisenbergEffect

@EisenbergEffect, have you had any chance to investigate this any further?

spapaseit avatar Jul 27 '16 13:07 spapaseit

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 :)

wk-plumbline avatar Aug 19 '16 07:08 wk-plumbline

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

mgerwinn avatar Aug 20 '16 10:08 mgerwinn

Any update on this? The quick fix method does not work in my case.

ignusk84 avatar Aug 21 '16 21:08 ignusk84

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.

khenderick avatar Sep 05 '16 04:09 khenderick

I'm facing this issue, as well. Any updates? Thanks!

abeam-intricity avatar Oct 05 '16 21:10 abeam-intricity

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

johot avatar Oct 21 '16 07:10 johot

please give the convention of =>

aditeeau avatar Nov 05 '16 10:11 aditeeau

Stale since 2016

Alexander-Taran avatar Mar 03 '18 19:03 Alexander-Taran

Ping @davismj to investigate since this relates to routing.

EisenbergEffect avatar Mar 03 '18 21:03 EisenbergEffect

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.

wzrdtales avatar Dec 23 '18 16:12 wzrdtales