-
Notifications
You must be signed in to change notification settings - Fork 26.3k
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
Conversation
98098e5
to
cf5e581
Compare
export type DetachedRouteTree = typeof Object; | ||
|
||
/** | ||
* @whatItDoes Provides a way to customize when and activated routes get reused. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and -> an / gets
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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}); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
attach ?
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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)) { |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
finer type ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Introduce the inner type
There was a problem hiding this comment.
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); | |||
|
|||
|
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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 => { |
There was a problem hiding this comment.
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', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"detachmenting" :)
cf5e581
to
d301b19
Compare
@@ -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. |
There was a problem hiding this comment.
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)) { |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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'); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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}); |
There was a problem hiding this comment.
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 = |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (this.activated) ...
eecd53a
to
42cf06f
Compare
Got an lgtm from vicb |
attach(ref: ComponentRef<any>, activatedRoute: ActivatedRoute) { | ||
this.activated = ref; | ||
this._activatedRoute = activatedRoute; | ||
this.location.insert(ref.hostView); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert empty before insert ?
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? |
@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 |
This would be nice if the document could have an example about how to properly use For example, can that be helpful for such situation: Router:
Now if you click on the image (ex: How can I use |
Don’t know why they miss this in documentation. |
Facing the same issue @m98, do they have any updates? |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
No description provided.