Skip to content

feat(router): add support for custom route reuse strategies #13124

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 30, 2016

Conversation

vsavkin
Copy link
Contributor

@vsavkin vsavkin commented Nov 29, 2016

No description provided.

@vsavkin vsavkin changed the title wip feat(router): add support for custom route reuse strategies Nov 29, 2016
@vsavkin vsavkin added the action: review The PR is still awaiting reviews from at least one requested reviewer label Nov 29, 2016
export type DetachedRouteTree = typeof Object;

/**
* @whatItDoes Provides a way to customize when and activated routes get reused.
Copy link
Contributor

@vicb vicb Nov 29, 2016

Choose a reason for hiding this comment

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

and -> an / gets

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

/**
* Determines if this route (and its subtree) should be detached to be reused later.
*/
abstract shouldDettach(route: ActivatedRouteSnapshot): boolean;
Copy link
Contributor

Choose a reason for hiding this comment

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

detach (single t)

@@ -287,6 +288,16 @@ type NavigationParams = {
promise: Promise<boolean>
};

export class DefaultRouteReuseStrategy implements RouteReuseStrategy {
Copy link
Contributor

Choose a reason for hiding this comment

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

Add a comment describe the strategy, ie "// Reuses only when the same route is activated"

@@ -326,6 +337,8 @@ export class Router {
*/
urlHandlingStrategy: UrlHandlingStrategy = new DefaultUrlHandlingStrategy();

routeReuseStrategy: any = new DefaultRouteReuseStrategy();
Copy link
Contributor

Choose a reason for hiding this comment

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

any ?

route: TreeNode<ActivatedRoute>, parentOutletMap: RouterOutletMap): void {
const outlet = getOutlet(parentOutletMap, route.value);
const componentRef = outlet.detach();
this.routeReuseStrategy.set(route.value.snapshot, <any>{componentRef, route});
Copy link
Contributor

Choose a reason for hiding this comment

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

is <any> required here ?

@@ -67,6 +67,13 @@ export class RouterOutlet implements OnDestroy {
return this._activatedRoute;
}

detach(): ComponentRef<any> {
this.activated = null;
return this.activated;
Copy link
Contributor

Choose a reason for hiding this comment

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

needs test (& fix)

return this.activated;
}

reattach(ref: ComponentRef<any>) { this.activated = ref; }
Copy link
Contributor

Choose a reason for hiding this comment

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

attach ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Need to update the activated route ?


if (this.routeReuseStrategy.shouldAttach(future.snapshot)) {
const stored = (<any>this.routeReuseStrategy.get(future.snapshot));
outlet.reattach(stored.ref);
Copy link
Contributor

Choose a reason for hiding this comment

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

componentRef ?

@@ -1151,6 +1191,11 @@ class ActivateRoutes {
}
}

function advancedActivatedRouteNodeAndItsChildren(node: TreeNode<ActivatedRoute>): void {
Copy link
Contributor

Choose a reason for hiding this comment

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

advanceActivatedRouteNodeAndItsChildren (drop the first d)

return new TreeNode<ActivatedRoute>(value, children);

} else if (!!routeReuseStrategy.get(curr.value)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

  • useless !!
  • not sure to get the logic here

*
* @experimental
*/
export type DetachedRouteTree = typeof Object;
Copy link
Contributor

Choose a reason for hiding this comment

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

finer type ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Introduce the inner type

Copy link
Contributor

Choose a reason for hiding this comment

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

Could we make it more explicit ?
Would it help with the cast DRT -> DRTI ?

export interface DRT {}

export interface DRTI extends DRT {
  // ...
}

@@ -31,6 +31,7 @@ describe('Integration', () => {
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
const fixture = createRoot(router, RootCmp);


Copy link
Contributor

Choose a reason for hiding this comment

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

nit: revert

@@ -2392,6 +2393,108 @@ describe('Integration', () => {
})));
});
});

describe('Custom Route Reuse Strategy', () => {
class AttacheDetachReuseStrategy implements RouteReuseStrategy {
Copy link
Contributor

Choose a reason for hiding this comment

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

Attache no e but may be reuseStrategy ?

return false;
} else {
let equal = true;
Object.keys(future.params).forEach(k => {
Copy link
Contributor

Choose a reason for hiding this comment

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

let equal = Object.keys(future.params).every(...)

}
}

it('should support attaching & detachmenting fragments',
Copy link
Contributor

Choose a reason for hiding this comment

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

"detachmenting" :)

@@ -287,6 +288,20 @@ type NavigationParams = {
promise: Promise<boolean>
};


/**
* Does not detach any subtress. Reuses routes as long as their route config is the same.
Copy link
Contributor

Choose a reason for hiding this comment

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

subtrees

function createNode(
routeReuseStrategy: RouteReuseStrategy, curr: TreeNode<ActivatedRouteSnapshot>,
prevState?: TreeNode<ActivatedRoute>): TreeNode<ActivatedRoute> {
if (prevState && routeReuseStrategy.shouldReuseRoute(curr.value, prevState.value.snapshot)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

comment ?

return new TreeNode<ActivatedRoute>(value, children);

} else if (routeReuseStrategy.retrieve(curr.value)) {
const tree: TreeNode<ActivatedRoute> = (<any>routeReuseStrategy.retrieve(curr.value)).route;
Copy link
Contributor

Choose a reason for hiding this comment

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

use the internal type ?

throw new Error('Cannot reattach ActivatedRouteSnapshot created from a different route');
}
if (curr.children.length !== result.children.length) {
throw new Error('Cannot reattach ActivatedRouteSnapshot with a different number of children');
Copy link
Contributor

Choose a reason for hiding this comment

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

not sure about this check ?

@@ -67,11 +67,26 @@ export class RouterOutlet implements OnDestroy {
return this._activatedRoute;
}

detach(): ComponentRef<any> {
this.location.detach(0);
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 not specify the optional 0 (same for insert)
Makes me think there are more than 1 view.

attach(ref: ComponentRef<any>, activatedRoute: ActivatedRoute) {
this.activated = ref;
this._activatedRoute = activatedRoute;
this.location.insert(ref.hostView, 0);
Copy link
Contributor

Choose a reason for hiding this comment

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

assert length == 0 ?

export type DetachedRouteTree = typeof Object;

/**
* @internal
Copy link
Contributor

Choose a reason for hiding this comment

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

add a comment (on DetachRouteTree ?) opaque vs internal

route: TreeNode<ActivatedRoute>, parentOutletMap: RouterOutletMap): void {
const outlet = getOutlet(parentOutletMap, route.value);
const componentRef = outlet.detach();
this.routeReuseStrategy.store(route.value.snapshot, <any>{componentRef, route});
Copy link
Contributor

Choose a reason for hiding this comment

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

remove <any> ?

this.activateChildRoutes(futureNode, null, outletMap);

if (this.routeReuseStrategy.shouldAttach(future.snapshot)) {
const stored: DetachedRouteTreeInternal =
Copy link
Contributor

Choose a reason for hiding this comment

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

can we say const stored = <DetachedRouteTreeInternal>this.routeReuseStrategy.retrieve(future.snapshot); ?

} else if (routeReuseStrategy.retrieve(curr.value)) {
const tree: TreeNode<ActivatedRoute> = (<any>routeReuseStrategy.retrieve(curr.value)).route;
setFutureSnapshotsOfActivatedRoutes(curr, tree);
return tree;
Copy link
Contributor

Choose a reason for hiding this comment

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

question: should we store null at this point ?
/bookmark 1

if (this.routeReuseStrategy.shouldDetach(route.value.snapshot)) {
this.detachAndStoreRouteSubtree(route, parentOutletMap);
} else {
this.deactiveRouteAndOutlet(route, parentOutletMap);
Copy link
Contributor

Choose a reason for hiding this comment

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

/ref bookmark 1
or store null here so that we never re-use an outdated value ?

@@ -67,11 +67,26 @@ export class RouterOutlet implements OnDestroy {
return this._activatedRoute;
}

detach(): ComponentRef<any> {
this.location.detach(0);
Copy link
Contributor

Choose a reason for hiding this comment

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

if (this.activated) ...

@vsavkin vsavkin force-pushed the router_caching branch 2 times, most recently from eecd53a to 42cf06f Compare November 30, 2016 07:21
@vsavkin
Copy link
Contributor Author

vsavkin commented Nov 30, 2016

Got an lgtm from vicb

@vsavkin vsavkin added pr_state: LGTM and removed action: review The PR is still awaiting reviews from at least one requested reviewer labels Nov 30, 2016
@vsavkin vsavkin merged commit 42cf06f into angular:master Nov 30, 2016
attach(ref: ComponentRef<any>, activatedRoute: ActivatedRoute) {
this.activated = ref;
this._activatedRoute = activatedRoute;
this.location.insert(ref.hostView);
Copy link
Contributor

Choose a reason for hiding this comment

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

assert empty before insert ?

@rmcsharry
Copy link

I see that there is a basic blog post about this here, but it's rather basic and seems to indicate the the reuse strategy is implemented application wide.

Has anyone written a more simple guide than this? Or a way to implement reuse for one specific component only?

@tjaartvanderWalt
Copy link

tjaartvanderWalt commented Jan 11, 2017

@rmcsharry I am also looking for a more detailed post/example. The implementation found here breaks when I have paths that contain child paths, I have opened an issue 13869. I have also posted the issue on SO

@m98
Copy link

m98 commented Nov 18, 2017

This would be nice if the document could have an example about how to properly use RouteReuseStrategy

For example, can that be helpful for such situation:

Router:

{path: 'news/:id', component: NewsComponent},  // to read one news
{path: 'news/:id/image/:image-id', component: NewsComponent},  // to see one news image by the id

Now if you click on the image (ex: news/123/image/5) the image opens by the logic we have in the component, but the component also re-initialize which means it will rerun the component methods which will retrieve news data over HTTP request.

How can I use RouteReuseStrategy for this case?

@sandangel
Copy link

Don’t know why they miss this in documentation.

@FKholbutayev
Copy link

Facing the same issue @m98, do they have any updates?

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 14, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants