aurelia / framework

The Aurelia 1 framework entry point, bringing together all the required sub-modules of Aurelia.
MIT License
11.75k stars 625 forks source link

Layout recreated on router navigation #804

Open dplutino opened 7 years ago

dplutino commented 7 years ago

The issue is described here: https://stackoverflow.com/questions/43257645/aurelia-layout-is-recreated-on-every-route-change

thinkOfaNumber commented 7 years ago

OP here, I've subscribed to this thread if you want more info :)

wzrdtales commented 5 years ago

Just stumbled upon this and am suprised that this is already that old :o of a bug.

In my case this is not about handling state across route changes on the layout level, but efficiency and an attempt to solve multi layout problems.

But first things first:

I have an application, where I have a login screen, which is very rudymental nothing special and the actual application screens, which includes elements like a navbar and sidebars, which are stable across the whole application. Only the one viewport that holds dynamic content actually updates. Except for the login scenario we do not break out of this.

Now, I solved this with this:

  <router-view layout-view-model="dash_layout"></router-view>  

This first solved what I looked to solve, but really only looked like it. In fact I now realized, that aurelia will rerender EVERYTHING of the layout on EACH route change. This is a nightmare, so I will need to stop using layouts here too. Now I probably will be left with nasty workarounds, since there doesn't seem a real solution to a simple problem like this. If there is a solution to this really simple problem, I would be more than grateful if someone can help out :)

So why is this a nightmare?

It is very simple and comes down to the following:

Rerendering the whole layout is...

So I get that this is just how layouts are supposed to work, but I didn't see a possibility to do the same thing with child routers, unless I want to stuff everything in a subroute. So I actually would need a main router as the child router, which does not seem to be possible without workarounds.

EisenbergEffect commented 5 years ago

I definitely wouldn't use layouts for this scenario. In your case, you might want to call setRoot with your login screen and then after login is complete, call setRoot a second time with your app screen. In that app screen you would have the router-view surrounded by normal components for your sidebar, etc. This workshop (which is now 100% free to stream) https://vimeo.com/ondemand/intermediateaurelia demonstrates a similar type of approach.

If that doesn't work for you, we can discuss some other strategies. There's definitely more efficient ways to handle this without adding a lot of complexity and completely avoiding unnecessary rendering :) Let us know if this helps out.

wzrdtales commented 5 years ago

@EisenbergEffect Actually exactly what I am testing out currently, having read the setroot suggestion over here https://discourse.aurelia.io/t/solved-use-child-router-for-main-routes/678.

wzrdtales commented 5 years ago

Just need to find out how to access setRoot outside of main.js

jwx commented 5 years ago

@wzrdtales Is multiple viewports an option for you?

wzrdtales commented 5 years ago

@EisenbergEffect After some extensive testing, it seems I have to draw the conclusion that also setRoot seems to be pretty broken for such use case scenarios.

What I have got working is that I can change the root now and also can get back from the login screen, and switch back to the app. So far sounds like success, but only if applications would be that simple in reality :/ . So first of all I stumbled over this bug here: https://github.com/aurelia/framework/issues/400

And actually fixed it very easily:

  login() {
    this.authorizeStep.loggedIn = true;
    this.aurelia.setRoot(PLATFORM.moduleName('app'));
    this.router.navigate('/');
    return true;
  }

The router navigate bit is important here. If I do not instruct the router to navigate somewhere it will keep stucked here and just display basically nothing at all (it displays the raw app.html though, but doesn't execute its contents).

So this seems to be related to the next problem.

Next thing up here is: Obviously there is more than just login, there is password forgotten and some other "logged out" pages. All that should use the root for non auth and all other should use authorized one. So this is not possible, since the new root element seems to reuse the router of the app side. This could be handleable, but actually here clearly is an unexpected behaviour or bug in the router. Why?

Let me elaborate, the router is doing a quite simple thing: If I instruct it to navigate somewhere, it will take me back into the "app" root where it was created instead. There seems to be no way to circumvent this, and since router-view is simply disfunctional (configureRouter gets also never called) on the second root, I effectively loose routing functionality on the second root.

Last but not least, when switching between a root element or setting it, it does not reset it when going back and forth. This will cause problems with things that might be overwritten on one side, like css.

So here is complete truck load of problems with that approach and is completely unusable due to that. I do not have any idea anymore how to solve this at this point, since all the problems at least seem to reside in aurelia itself.

wzrdtales commented 5 years ago

@jwx Not sure, please elaborate, I already explained what I want: two different kinds of page layouts with different layouts. One for the application when it is authorized, one when it is unauthorized.

jwx commented 5 years ago

@wzrdtales Well, how about having two viewports, one for the unauthorized and one for the authorized, and setting content in the appropriate one. And then you just hide the empty one (and possibly its surroundings if you need to).

wzrdtales commented 5 years ago

That would be a very nasty workaround to do this, could work, but would limit drastically when it comes to optimizing. Especially optimizing loading behaviors. But it seems as there is no possibility in aurelia without working around it currently :/. So that would be an option, but not one I would want to choose.

jwx commented 5 years ago

@wzrdtales I must be missing something or lack some information, because I'm not seeing the nasty problems you're describing. As far as I know, there are no real penalties to having more than one viewport and I wouldn't think hiding some elements, with css or binding or whatever, should be so problematic.

Regardless, if you do find a solution, please share it since there's some interest in layouts and viewports and similar.

wzrdtales commented 5 years ago

@jwx With viewports I can't reset any css, I will need to do that dynamically with executed code instead, since I will end up otherwise with the exact same problems again. Rerendering too much and resetting contextes and the other examples I already wrote about here.

But last but not least, it is just not a solution. It is a workaround and that means always that there is unwanted behaviour included and also that the solution is not clean. If with setRoot it would have worked, this would have been perfect. Of course also setRoot should be more easily accessable, but hey there is always room for improvement. However using multi viewports is not really a solution and would be just a workaround with the given caveats already to be expected. Seems like I will ditch Aurelia this time again :(, am a bit frustrated that I need to say this, I really really like how Aurelia aims to solve problems and have to say you all made an awesome job here. I like aurelia out of all the frontend toolkits/frameworks best, but that doesn't helps me if I want to produce a qualitative product with it, that will pass my standards (which are obviously very high). And in this way with this kind of workarounds this is just not possible. And therefor I will not build my usual tooling, when deciding for a technology for the next bigger segment of time (basically a lot of reusable components and reusable highly optimized setups).

With that I guess, I hope next time I reevaluate (looking at your efforts of vNext (hopefully you wont enforce typescript on the users, otherwise you lost me there)) things feel as smooth as they felt for the current project and this kind of problems are being solved there. So continue your good job on this!

EisenbergEffect commented 5 years ago

What do you mean by accessibility of setRoot? You can inject Aurelia into any class in your app and call setRoot. Do you mean something else by accessibility?

But for different types of page layouts, you can use some simple techniques that have been in practice in vary large-scale apps (such as VS, etc) for probably 20+ years. Simply model your shell structure itself with different pluggable locations and allow content to call APIs that allow pushing different UI widgets into those regions. You can even write a router plugin that does this based on route metadata. As the leader of this framework, I personally prefer this technique over using router layouts. I've been using it for over ten years. It allows for any sort of optimization and when accomplished by using the compose element at the different shell locations, enables endless extensibility for the app in the future. I typically use some version of this technique in almost every app I build and Aurelia was designed to do this very simply with just POJOs, compose, and bindings. I'd recommend that you explore this as an option for your design before switching frameworks entirely.

(PS: vNext won't force the use of TypeScript.)

wzrdtales commented 5 years ago

(PS: vNext won't force the use of TypeScript.)

Thats good to hear :)

With accessibility I mean first of all, accessibility in terms of usability and visibility. That starts with documenting it, there is actually little to no information about that in the docs yet (except for a tiny auto generated api bits) and ends up with simplifying it. So for example thinkable would be instead of calling setRoot a possible simplified alternative would be instructing the router directly to use a different root. I know that this would be currently completely outside of the concept how it works since the router is a component of the root and not the other way around, this is solely meant as an example.

For your suggestion, do you have an example, or something you can reference for this. Since this sounds like what I wanted to do already, but with layouts (since I found no good alternative and did not wanted to control it necessarily over stupid routes) :/. And how do POJOs relate here now oO

wzrdtales commented 5 years ago

So I gave it another chance and looked at it from multiple ways again. Unfortunately, not a single one works. Since I don't know how exactly you wanted to implement your suggestion @EisenbergEffect , this probably wasn't tested. Anyways, the further I dig here, the worse it gets...

@jwx suggestion doesn't even work, even though I wouldn't want to do this anyway. However if I set an if.bind on the surrounding div element that should disappear, it completely breaks aurelia. But without showing any errors actually. What I know is, that this is due to the router-view element within the div. Other div elements work fine with if.bind though. So the only way to use this suggestion would be to hide all components inside this structure, but do not remove the structure which will be unused.

So I really have to come to the conclusion, that building multiple shell layouts is just not a thing possible at all with aurelia. At least not if you want to solve it for real and have it clean and performant. Neither can be accomplished as it seems and I am finally giving up on this. I will need to stick with react as it seems. Which is really unfortunate, as I do not wish to. Thanks for your time anyway.

EisenbergEffect commented 5 years ago

I guarantee that it's more than possible. It's been done before many times in different ways. I've built things much more complicated than what's described here with no perf issues. Each app is different and has different concerns to take into consideration and it's hard reading this thread for me to figure out just what things matter to you and what you consider "bad" or "unacceptable" and why. Techniques that would be unperformant with React or other frameworks will not have any issue with Aurelia and Aurelia has no problems lazy loading anything at any level at any time.

If you are open to learning new techniques and exploring other approaches, you should engage on discourse.aurelia.io, which is where the entire community goes for help with questions of "how to" and architecture. We use GitHub to track bugs and features, which we'll use this thread for, as there are some improvements to be made. But, for more discussion on how to architect your solution, please move the conversation to Discourse and try to proceed without threatening to ditch the framework. That tends to push people away. Aurelia is more than capable and there are people on the forum who would love to help you learn the best way to handle your design. If you've exhausted that avenue and are still unable to find a solution, we have core team members that can provide commercial support or guidance as well which would also provide support for the framework itself.

davismj commented 5 years ago

I think I understand where @wzrdtales is coming from. setRoot does work, but it is not nearly as robust and powerful as other features in Aurelia. It's actually worked its way up my priority queue of things I want to improve in vCurrent.

@wzrdtales if you could either make a quick list of things you expect to be able to do and how, and bonus points if you could add some failing tests to a branch, that would help make sure any improvements that I make meet your expectations.

JontyMC commented 5 years ago

Is this issue being addressed in v2? We have the same problem.

We have authentication pages (login, signup, reset password, etc) that have a completely different shell to the main app pages. We started with setRoot, but that had issues eg #400, so we switched to layouts. We didn't realise layouts reloaded on every route change and this is now causing flickering in the UI and perf issues (which makes layouts practically unusable).

Thinking more about how I would like this to work, is it possible to have routing changes simply call a function (ie bypass the router)? This way there could be a top level compose element that could be manually switched between Auth and App view models. Each of these can have a compose element that can also be switched manually when the route hasn't changed between Auth and App.

This approach is more intuitive than either setRoot or layouts and would give more fined grained control over module creation, lifecycle and custom piplelines. It would also allow routing to play more nicely with aurelia state, as you could change values in the state at whatever points in your custom pipleline/lifecycle.

Is it possible today to hook into the router this way? Ie, I would like to define routes and be notified of route changes, but not actually have the router manage module creation, lifecycle or use the router-view. Any downsides to this approach?

jwx commented 5 years ago

@JontyMC I wouldn't do parts of the router's job since there's a fair amount of moving pieces involved and there's a lot that then would need to be done manually (and kept up to date). The router of Aurelia 2 should however support your use case (the way I understand it) since viewports can be placed within components/custom elements without their surroundings updating when the viewports get new content.

JontyMC commented 5 years ago

This is kind of my point. I don't want lots of moving pieces and complexity I don't need. I also want full control over lifecycle, module creation and when/where I replace modules.

If I can be subscribe to routing changes, I can

What does having the router give me that this doesn't? I think this more elegant, simpler and flexible than viewports.

jwx commented 5 years ago

You're basically describing the job that the router does and what it gives you. But rolling your own routing solution will of course give you a solution that fits your requirements perfectly with nothing more and nothing less. If you have the time for it, and especially if you have specific requirements, it's definately one way to go. (This goes for any part of the framework.)

If you're doing all of the above yourself, though, there really isn't any router left to send out routing changes. But if you've got the time and will to do the above, dealing with links, url changes and browser buttons is a small addition. Also, it'd probably be needed for full control.

If you do end up rolling your own routing solution, it'd be interesting to know how it turns out so if you can and want to, please share. Good luck!