router icon indicating copy to clipboard operation
router copied to clipboard

Suggestion: router.reload()

Open jods4 opened this issue 9 years ago • 49 comments

Feature description

It would be nice to have an API router.reload() or similar. Basically it would navigate to the current route.

Respecting activation strategies means that it could do nothing, create a new VM or just invoke the lifecycle again. I am unsure if passing options to force a different activation strategy (e.g. invokeLifecycle instead of default) is useful or not.

As I am not using child routers or viewports, am I unsure what behavior would make sense for those.

Use cases

Use cases could be that you've performed an operation on the server (e.g. save) and the impact on your VM is complicated and you prefer to load it from scratch than try to update it.

Current solutions

Trying to use navigate or navigateToRoute is currently doomed to fail, because of this check I think: https://github.com/aurelia/history-browser/blob/master/src/index.js#L191

A workaround is to pass an additional "forcing" parameter with a random value or something that changes such as time.

jods4 avatar Aug 29 '15 00:08 jods4

@bryanrsmith I think this is similar to the other issue we had where we proposed something like a force option to be added to the options for the navigate method. I'm seeing this issue come up more and more, so I think we definitely want to find a clean way to address it.

EisenbergEffect avatar Aug 29 '15 00:08 EisenbergEffect

@EisenbergEffect Yes, a new option force: true that would skip the check on line 191 I referenced in my suggestion would certainly do the trick.

The method reload would still be a nice convenience wrapper to call navigate(this.fragment, { replace: true, force: true }) and is certainly more discoverable.

jods4 avatar Aug 30 '15 10:08 jods4

That sounds good. @bryanrsmith At your earliest convenience, let's work on adding the force parameters and then creating the reload or maybe refresh method that is a convenience as stated above.

EisenbergEffect avatar Aug 30 '15 17:08 EisenbergEffect

Hi,

After some time looking for a response I couldn't find any way I can refresh the current view. I'd like to have a way to reload the current view and forcing all the cycle to run

dpinart avatar Nov 06 '15 16:11 dpinart

+1 to this feature

kiryaka avatar Nov 28 '15 22:11 kiryaka

It's surprising there's no way to reload/refresh current view/view-model... I'd just expect activate method to be called again

dpinart avatar Nov 28 '15 23:11 dpinart

+1 Is there any way to achieve that ?

jvkassi avatar Dec 31 '15 13:12 jvkassi

This is tricky. What would it mean to refresh if there were child routers? Would you expect the entire hierarchy to be refreshed? or would you expect only the deepest child to be refreshed? Perhaps this would just be controlled with navigation strategies, but what if you want different strategies for the same view model based on whether or not there's an internal refresh?

A simple way to achieve the use case above, is to extract all state into its own class and simply set that as a property on the screen. Then, whenever you want to "refresh" you simply replace the property with a new instance of the state class. You could make this really simple by manually calling the screen's own activate callback. Here's some pseudo code:

@inject(Server)
export class MyScreen {
  constructor(server) {
    this.server = server;
    this.params = null;
    this.model = null;
  }

  activate(params) {
    this.params = params;
    return this.configureModelFromParams(params);
  }

  reset() {
    this.activate(this.params);
  }

  configureModelFromParams(params) {
    return this.server.loadModel(params.id).then(model => this.model = model);
  }
}

In this case I'm showing loading a model from the server, but it could be some other more complex aggregate model derived from anything. The point is that the state doesn't reside directly in the screen. It resides in some other object that you can easily dispose of and replace. The screen then acts more like a controller, automating the process of creating that object and replacing it as needed. Additionally, this is going to be much more efficient because the screen object and its view don't need to be destroyed, re-created, rebound, etc. Only the underlying data of the screen is destroyed and recreated. The binding system will handle the rest.

EisenbergEffect avatar Dec 31 '15 14:12 EisenbergEffect

Our use case is changing permissions for the current user. Imagine the admin user who is going to the user management and change his own roles and permissions. We need the full system refresh. Everything depends on the user permissions from the very beginning, including the navigation. May be with the new permissions user will not even be able to stay on the page where he currently is. The easiest solution is user re login, but I think we can do better then this.

Another use case is back end system ask front end to refresh because last user activity was too long ago and the system state is inconsistent now. Again, we need the complete refresh.

kiryaka avatar Dec 31 '15 15:12 kiryaka

I use this (with a ugly hack: add a dummy random parameter to the current route) quite a bit in our app.

One reason (not the only one) is that we use one-time bindings where we can, so when we want a full refresh we need to re-evaluate those bindings, too.

It's not the only reason, another being that @EisenbergEffect's example is a basic model. We have complex UI with multiple independent parts and not everything can easily be reduced to a single loadModel call. Plus there might be some UI state we want to reset, which is not always tied to a ViewModel property (see the motivations of #173, notably my comments at the bottom).

As we don't use child routers, I am not sure if refreshing child routers is the right thing to do or not... I would lean towards yes? Could it simply be an option to the new api, with a true default value? Maybe: router.reload({ childRouters: false }) ?

jods4 avatar Jan 02 '16 19:01 jods4

@EisenbergEffect @bryanrsmith I think that in the case of child routers - if you call reload() on a parent router, it would reload itself and all of its children (unless you specifically wanted to opt-out of that behavior for some reason). The most logical solution for reloading only the deepest child, to me, is to call reload() on that specific child. (The code would have to have a reference to it somewhere, anyway.)

This would ensure that there's only a one-way flow at all times, and a predictable one, at that. A router either only reloads itself or its entire hierarchy, but never a parent router or an arbitrary child router.

Finally, I think the reload function could take a NavigationStrategy as an optional parameter, if you wanted to override the configured strategy. Or, this may not be necessary at all.

If there are use cases where this would not work, let's explore them. I'd like to nail this down before we begin work on it.

jwahyoung avatar Feb 13 '16 06:02 jwahyoung

That sounds reasonable to me. It might be nice to get feedback from someone who wants this feature, though. The simplest thing to do (in terms of implementation on our end) is to reload the entire router tree, respecting activation strategies. I think that would work now, there's just no helper method for making it happen.

bryanrsmith avatar Feb 13 '16 21:02 bryanrsmith

Is there still a plan to add the force parameter to the router/navigateToRoute's option?

xiaoweil avatar Feb 14 '16 03:02 xiaoweil

@bryanrsmith "reload the entire router tree" will work for us. We have two use-cases for this feature. Both will be solved. Both of them is "permission based". Current user permissions changed and it pottentially affect everything on the app.

kiryaka avatar Feb 14 '16 14:02 kiryaka

@bryanrsmith We need this and reload all child routers would work for us as well, mostly because we don't have child routers ;)

jods4 avatar Feb 14 '16 14:02 jods4

+1 @bryanrsmith i was able to hack the old Alpha history-browser previously to reload same path with new dynamically loaded routes

// if (this.fragment === fragment) return;

aurelia/history-browser#4

Also similar use case to @kiryaka. Unauthorised routes are hardcoded into app. Once authorised, we need to reset router and navigate to same blank route, now configured with authorised routes. Current call sequence was/is:

router.reset();
router.configure((config) => { ... });
router.navigate(path || '', { replace: true });

ochart2 avatar Feb 15 '16 10:02 ochart2

We are also desperately waiting on a fix for this. Is there an ETA ? Thanks.

ashbrener avatar Mar 11 '16 12:03 ashbrener

+1

joelclimbsthings avatar Mar 14 '16 23:03 joelclimbsthings

also seems that this issue is closely related #237 (after router.reset() and router.configure() the view isn't updated)

ochart2 avatar Mar 15 '16 13:03 ochart2

I am having a similar issue in which some pages are not being updated when the route changes. I see some mention of (with an ugly hack: add a dummy random parameter to the current route). Does anyone have a sample of how this hack is implemented? Thanks.

DanielRNichols avatar Mar 18 '16 01:03 DanielRNichols

The concern with this issue is that we we have to do a page refresh on the browser every time a user authenticates (which is when we generate the navigation menu's).

The result of this is that page load feels like it takes 15sec when in actual fact it would take 3sec.

yawn

ashbrener avatar May 03 '16 12:05 ashbrener

For authentication, one solution is to route the user to a "logged in page" or profile page but I want the user to stay on the current page (but refresh it based on the new authenticated status). I solved this scenario by sending an event when the user authenticates. Each view is then responsible to subscribe to this event and react accordingly. Some views does nothing, some update backing fields in the view model which in turn will update the bound elements in the view.

I do however agree with this suggestion and use case.

mikeesouth avatar May 03 '16 16:05 mikeesouth

We also have the same problem in my company with the same usecases =>

  • Change User login which also changes language or permission
  • Change User settings (language or permission)

When doing so this results in wrong translated views or in wrongly shown views. There is currently no possibility to refresh a page you always have to use observers or something like this in order to get the views refreshed. This is a pain and should be nothing that you have to do on your own. I would like to see this feature in following releases.

BTW you're doing a great job with aurelia - thanks :)

nporcu avatar May 20 '16 14:05 nporcu

Update In case you missed it, it seems some changes have been made that impact this.

The culprit used to be the following line in history-browser if (this.fragment === fragment) return; Which was changed at some point to: if (this.fragment === fragment && !replace)

It's not clear to me why this was changed and replace is a bit orthogonal to "force reload". But the net result is that if you pass { replace: true } when navigating it will perform a reload even when you go to exactly the same route. Conveniently, using replace when reloading a page is highly likely as you probably don't want to add an history entry for that.

Note: don't forget to use

determineActivationStrategy() {
  return activationStrategy.invokeLifecycle;
}

or otherwise the lifecycle events of your VM won't be called by default.

jods4 avatar Jul 06 '16 14:07 jods4

@jods4, this is still an issue for us...

this.router.reset();
this.router.configure(function (config) {
    config.title = conf.title;
    config.map(conf.menus);
}).then(() => {
    this.router.navigate(path || '', { replace: true });
});

am I doing anything wrong to dynamically load the new routes?

ERROR [app-router] Error: There was no router-view found in the view for ./dashboard.

ochart2 avatar Jul 08 '16 11:07 ochart2

@ochart2 sorry, I really don't know what you're trying to do with the dynamic router configuration. Maybe @EisenbergEffect can help.

I am just using this.router.navigateToRoute(currentRoute, currentParams, { replace: true }) in our ViewModels and with the current release it seems to correctly reload the page.

jods4 avatar Jul 08 '16 11:07 jods4

@jods4 The idea is to reload the router with brand new routes, and then navigate to the emtpy route again, which should activate a different viewmodel from the one currently loaded. Does that make sense?

ochart2 avatar Jul 08 '16 11:07 ochart2

@jods4 ps getting the following when using navigateToRoute

Error: A route with name '' could not be found. Check that `name: ''` was specified in the route's config

with the names object in aurelia-route-recognizer empty

ochart2 avatar Jul 08 '16 11:07 ochart2

@jods4, got it working with navigateToRoute! Question, it is now manipulating the actual url by appending ?replace=true to it. Is there not a cleaner solution for this as I am now manually removing ?replace=true out of the url afterwards?

ochart2 avatar Jul 08 '16 12:07 ochart2

@ochart2 I think you messed up the parameters. navigateToRoute(routeName, routeParams, options) Note that there is one additional parameter compared to navigate().

jods4 avatar Jul 08 '16 12:07 jods4