Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

feat($route): add support for the reloadOnUrl configuration option #15002

Closed
wants to merge 3 commits into from

Conversation

gkalpak
Copy link
Member

@gkalpak gkalpak commented Aug 7, 2016

What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)
Feature.

What is the current behavior? (You can also link to an open issue here)
When the path changes and the new URL maps to the current route, the route is reloaded.
See #14999 (and some of the issues mentioned there).

What is the new behavior (if this is a feature change)?
The user can determine that specific routes should not get reloaded, when the path changes and the new URL maps to the current route.

Does this PR introduce a breaking change?
No.

Please check if the PR fulfills these requirements

Other information:
Enables users to specify that a particular route should not be reloaded after a URL change
(including a change in $location.path()), if the new URL maps to the same route.
The default behavior is still to always load the matched route when any part of the URL changes.

Related to #1699, #5860, #14999 (potentially closing the first two).

Fixes #7925.

@gkalpak
Copy link
Member Author

gkalpak commented Aug 7, 2016

I am not super happy with the API (reloadOnUrl is a little cryptic - as is reloadOnSearch). I would prefer something like:

reloadOnChange: {
  hash: true/false,
  search: true/false,
  url: true/false
}

But at this point I figured it is better to (a) keep consistent with the existing API and (b) not introduce multiple way to do the same thing. But we might want to consider deprecating the existing way and introducing a different API in a future version.

@gkalpak
Copy link
Member Author

gkalpak commented Aug 7, 2016

Btw, in the future we might want to make this more flexible by allowing the user to specify a function (or service) for determining whether to reload or not. This would allow to have a default value for the route, but overwrite it in specific cases.

@Mischi
Copy link

Mischi commented Aug 16, 2016

Both features where the original reason for @davidreher and me to choose the now deprecated ngComponentRouter. We would love to see see these features added to ngRoute!

@gkalpak
Copy link
Member Author

gkalpak commented Aug 17, 2016

Also, considering that Angular 2's latest router re-uses components by default (afaik), it might be a good idea to re-use components by default as well (for 1.6 or 1.7).

@Narretz
Copy link
Contributor

Narretz commented Nov 29, 2016

So this PR is basically a sub-set of the old problem where you wan't to change the location but not let Angular do anything (like change / reload a route).
With it, you can now "change the location to something that would map to the same route (but with different route params)"
The problem I see is that a static route definition doesn't really make the following use cases simpler (#1699, #3789):

So our route is like /thing/:id. When creating a new thing they go to /thing/new. Once the thing has been successfully saved we want to change the route to /thing/1234

Or when someone enters the wrong title for a particular ID. See Stackoverflow, when you change the title but enter just the ID, it will correct the title portion of the URL to be correct.

If the route never reloads on url change, if you do /thing/5678, then it also doesn't change the route, and you'd have to manually do work in the routeUpdate event (e.g. fetching data), instead of relying on resolves.

So I think we either need a $location function that doesn't trigger route change (don't really like that, though) or a dynamic way to decide if a route should be reloaded.

Another idea is to keep the template / DOM and re-run the resolves, and push the new data to the scope, so any components could handle data changes via onChanges or similar. This might be too much for vanilla ngRoute though.

@gkalpak
Copy link
Member Author

gkalpak commented Nov 29, 2016

The problem I see is that a static route definition doesn't really make the following use cases simpler (#1699, #3789)

This doesn't indeed cover #3789 (in a convenient way), but that sounds better handled with a redirection tbh.

Regarding #1699, I think this PR covers their exact usecase: You are on /thing/new, you create and save a new item (so at that point you have all the data you need for /thing/123), you change the URL to /thing/123 and the route doesn't get reloaded (including the resolves). If you need to also re-resolve the data, then you can handle that inside the controller (which by some is considered a better practice anyway).

--
WRT dynamically deciding whether a route should be reloaded or not:

  1. Having the controller/component decide is not a good idea imo. This is something that needs to be decided on a route level, so should be in Route Definition Object.

  2. Adding support for this by allowing a function or service as value of reloadOnSearch sounds like an interesting idea. It is still configurable on a per-route basis, but can be dynamic and the controller can have a saying via some DI'ed service.

Taking (2) one step further, we could introduce a new reloadStrategy property that accepts a service that implements the ReloadStrategy interface. This could replace both reloadOnSearch and reloadOnUrl. We could for example have built-in strategies for not reloading when the hash/search/path changes, but users could specify their own.

--

So I think we either need a $location function that doesn't trigger route change

In general, I believe $location should be kept out of this (unless we are talking about a url change that should not update $location's internal path/search/hash properties/state, which would be asking for serious trouble and abuse - but is a different discussion). If we are talking about ngRoute-specific behavior/features, it should be entriely included in ngRoute.

@miwillhite
Copy link

Any updates on this?

@Narretz
Copy link
Contributor

Narretz commented Nov 4, 2017

@miwillhite No, not at the moment.
To get some traction for this, you could test the patch and see if it would work for your use-case, or otherwise give some feedback on the behavior / implementation.

Btw, does ui-router have this functionality?

@petebacondarwin petebacondarwin modified the milestones: Backlog, 1.7.0 Jan 29, 2018
@petebacondarwin petebacondarwin modified the milestones: 1.7.0, 1.6.10 Feb 26, 2018
@petebacondarwin petebacondarwin modified the milestones: 1.6.10, 1.7.x May 2, 2018
@gkalpak gkalpak self-assigned this May 9, 2018
@petebacondarwin petebacondarwin self-assigned this May 14, 2018
@gkalpak gkalpak force-pushed the feat-route-reload-on-path branch from 4f54a92 to 96c71b0 Compare May 23, 2018 19:13
Copy link
Contributor

@Narretz Narretz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few smaller things.
The avanced API outlined in the PR would have been nice, but I assume that most people use uiRouter anyway at this point.

* If the option is set to `false` and the URL in the browser changes, then a `$routeUpdate`
* event is broadcasted on the root scope (without reloading the route).
*
* **Note:** This option has no effect if `reloadOnUrl` is set to false.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be more prominent, i.e. wrap it into a note block

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrapped.

@@ -544,8 +556,9 @@ function $RouteProvider() {
* @name $route#$routeUpdate
* @eventType broadcast on root scope
* @description
* The `reloadOnSearch` property has been set to false, and we are reusing the same
* instance of the Controller.
* Any of the `reloadOnSearch` and `reloadOnUrl` properties has been set to false and we are
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would change this to something like `This event is fired if we are reusing the same instance ... because any of ...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rephrased.

preparedRouteIsUpdateOnly = preparedRoute && lastRoute && preparedRoute.$$route === lastRoute.$$route
&& angular.equals(preparedRoute.pathParams, lastRoute.pathParams)
&& !preparedRoute.reloadOnSearch && !forceReload;
preparedRouteIsUpdateOnly =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be even more readable if it was in a separate fnction

Copy link
Member Author

@gkalpak gkalpak May 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functionized.

});
});


describe('reload', function() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this describe be a bit more explicit? There's already a top-level describe with this name. How about with reload()

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a top-level describe with the same name 😕.

But I agree the descriptions are not great (I copied them from the existing reloadOnSearch tests 😳).
I've updated both to with `$route.reload()` (since they are testing how the configs work in conjuction with $route.reload()).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I didn't see the reload() block was in reloadOnSearch

it('should reload a route when reloadOnSearch is enabled and .search() changes', function() {
var reloaded = jasmine.createSpy('route reload');
describe('reloadOnUrl', function() {
it('should reload a route when `reloadOnUrl` is enabled and `.url()` changes', function() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wording is inconsistent in the changes - mostly it's enabled but further below it's true (I prefer the latter).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made consistent.

@gkalpak
Copy link
Member Author

gkalpak commented May 26, 2018

Thx, @Narretz!
Comments adressed. PTAL

@Narretz
Copy link
Contributor

Narretz commented May 26, 2018

I think you forgot to push the changes @gkalpak 👀

@gkalpak gkalpak force-pushed the feat-route-reload-on-path branch from 96c71b0 to daec562 Compare May 26, 2018 19:43
@gkalpak
Copy link
Member Author

gkalpak commented May 26, 2018

Ooops 😳 Definitely pushed now 😃

@Narretz
Copy link
Contributor

Narretz commented Jun 6, 2018

LGTM - maybe @petebacondarwin should take another look?

$httpBackend.when('GET', 'bar.html').respond('bar');
$httpBackend.when('GET', 'baz.html').respond('baz');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this change have any effect?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No afaict 😁
(Except for ordering definitions in proper foo-bar-baz order, which is as great an effect as a change can have 😇)


inject(function($location, $rootScope, $routeParams) {
$rootScope.$on('$routeChangeStart', routeChange);
$rootScope.$on('$routeChangeSuccess', routeChange);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: if we care to test that both these are being triggered then they should have different spies. Otherwise we don't know that only one of the events is being triggered twice rather than each being triggered only once.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have other tests to verify that these are called correctly on route change.
Here we mainly want to verify that the routeChangeStart/Success combo is emitted and routeUpdate isn't.

expect(routeChangeSuccess).toHaveBeenCalledOnce();
expect($log.debug.logs).toEqual([['initialized']]);

$log.reset();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed

expect($log.debug.logs).toEqual([['initialized']]);
expect($routeParams).toEqual({param: 'bar'});

$log.reset();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, these calls are needed, because there is an afterEach() call that asserts the log is empty:

$log.assertEmpty();

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird because I thought there are other tests that do not call $log.reset() at the end but do cause logging...

By calling reset we are preventing the afterEach from checking that other logs were not posted. In any case this is a bit counter-intuitive. Perhaps we should have a helper that checks the log and then removes that item?

);


it('should reload when `reloadOnSearch` is true and url differs only in route path param',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'should reload when reloadOnSearch is true false and ...' ?

Copy link
Member

@petebacondarwin petebacondarwin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few minor nits but OLGTM

Enables users to specify that a particular route should not be reloaded after a
URL change (including a change in `$location.path()`), if the new URL maps to
the same route.
The default behavior is still to always load the matched route when any part of
the URL changes.

Related to angular#1699, angular#5860, angular#14999 (potentially closing the first two).

Fixes angular#7925
@gkalpak gkalpak force-pushed the feat-route-reload-on-path branch from daec562 to 956c727 Compare June 7, 2018 14:15
@gkalpak
Copy link
Member Author

gkalpak commented Jun 7, 2018

Fixed up and rebased.

@gkalpak gkalpak force-pushed the feat-route-reload-on-path branch from 956c727 to a9b6b21 Compare June 7, 2018 20:47
@gkalpak gkalpak closed this in 60069e6 Jun 8, 2018
gkalpak added a commit that referenced this pull request Jun 8, 2018
Enables users to specify that a particular route should not be reloaded after a
URL change (including a change in `$location.path()`), if the new URL maps to
the same route.
The default behavior is still to always load the matched route when any part of
the URL changes.

Related to #1699, #5860, #14999 (potentially closing the first two).

Fixes #7925

Closes #15002
@gkalpak gkalpak deleted the feat-route-reload-on-path branch June 8, 2018 13:17
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Something along the lines of reloadOnPathParams in ngRoute
6 participants