-
Notifications
You must be signed in to change notification settings - Fork 26.2k
refactor(router): remove extra CD run on activation #16510
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
2cc5f42
to
6a57a24
Compare
5cd66ea
to
cf6d4a1
Compare
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.
Left some questions.
My main question is the following:
Should not we create a tree of OutletContext altogether ahead of time, basically by writing this function:RouterStateSnapshot => OutletContext
.
Every node will have some nulls which will be filled in during activation, but the structure of the tree should be known ahead of time.
Right now you assemble the tree as you go, and I think it makes the code harder to follow.
What do you think?
} | ||
|
||
getContext(childName: string): OutletContext|null { | ||
const info = this.contexts.get(childName); |
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.
maybe rename info
into context
for consistency
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.
right, I missed this renaming.
registerOutlet(name: string, outlet: RouterOutlet): void { this._outlets[name] = outlet; } | ||
onChildOutletDestroyed(childName: string): void { | ||
const context = this.getContext(childName); | ||
if (context) { |
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.
when would it be null?
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.
Agreed. This is to please TS as getContext
could return null. We should really have an assert
*/ | ||
removeOutlet(name: string): void { this._outlets[name] = undefined !; } |
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.
RouterOutletMap is a public API, so removing this is a breaking change.
resolver: ComponentFactoryResolver|null = null; | ||
outletMap = new RouterOutletMap(); | ||
attachRef: ComponentRef<any>|null = null; | ||
} | ||
|
||
/** | ||
* @whatItDoes Contains all the router outlets created in a component. | ||
* | ||
* @stable | ||
*/ | ||
export class RouterOutletMap { |
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.
Since you already making it a breaking change, do you think it makes sense to rename this class into something like OutletContextChildren. I think this name would work better.
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.
Agreed.
I still need to discuss how to handle breaking changes for this PRs. I have refrained from doing more breaking changes in this PR before discussing with the team.
|
||
@Output('activate') activateEvents = new EventEmitter<any>(); | ||
@Output('deactivate') deactivateEvents = new EventEmitter<any>(); | ||
|
||
constructor( | ||
private parentOutletMap: RouterOutletMap, private location: ViewContainerRef, | ||
private resolver: ComponentFactoryResolver, @Attribute('name') private name: string) { | ||
parentOutletMap.registerOutlet(name ? name : PRIMARY_OUTLET, this); | ||
private resolver: ComponentFactoryResolver, @Attribute('name') name: string) { |
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.
maybe you could do @Attribute('name') private name: string = PRIMARY_OUTLET
ngOnDestroy(): void { this.parentOutletMap.removeOutlet(this.name ? this.name : PRIMARY_OUTLET); } | ||
ngOnDestroy(): void { this.parentOutletMap.onChildOutletDestroyed(this.name); } | ||
|
||
ngOnInit(): 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.
Why is it done in this ngOnInit and not in the constructor? Cause the parent's change detection will run before either constructor or ngOnInit.
And in the constructor this.activated
will always return false.
ngOnDestroy(): void { this.parentOutletMap.onChildOutletDestroyed(this.name); } | ||
|
||
ngOnInit(): void { | ||
if (!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.
Since there are three levels of nested if
, maybe add a comment to each branch, so it is clear what triggers each condition.
packages/router/src/router.ts
Outdated
|
||
// if we have a componentless route, we recurse but keep the same outlet map. | ||
// If we have a normal route, we need to go through an outlet. | ||
const context = parentOutletMap.getOrCreateContext(future.outlet); |
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.
Seems strange that we can create a new context during deactivation. Should not it only happen during activation?
1f831ff
to
e660488
Compare
- don't run change detection every time an outlet is activated - drop `routerOutletMap`s caused by angular/angular#16510
- don't run change detection every time an outlet is activated - drop `routerOutletMap`s caused by angular/angular#16510
- don't run change detection every time an outlet is activated - drop `routerOutletMap`s caused by angular/angular#16510
- don't run change detection every time an outlet is activated - drop `routerOutletMap`s caused by angular/angular#16510
- don't run change detection every time an outlet is activated - drop `routerOutletMap`s caused by angular/angular#16510
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. |
Problem
Before this PR, the information about the child outlet were stored in a
RouterOutletMap
on the parentRouterOutlet
directive.This means that all the
RouterOutlet
s need to be created when a route is activated.In order to instantiate the nested
RouterOutlet
, the CD need to run each time a segment is activated in order to create the nestedRouterOutlet
.Running the CD multiple times causes trouble with the animation.
Solution
As we do not want to run the CD multiple times, we can not rely any more on the outlets being present when a route is activated. Then we can not store the
RouterOutletMap
on theRouterOutlet
.RouterOutletMap
now forms a tree that contain all the information about the currently active route.When a route is activated, if the
RouterOutlet
is already instantiated it is immediately activated.However when the
RouterOutlet
is instantiated yet, it will be activated in itsngOnInit
lifecycle hook.Other notes
In the former implementation it was possible to detect when a template misses a
RouterOutlet
because they must be present while the route is activated.With this PR it is not possible to detect that any more because
RouterOutlet
do not have to be instantiated while a Route is activated.I think that the behavior after the PR is more reasonable:
Before:
cond
wasfalse
while the route is activated this would be an error as the outlet would not exist when the route is activated,cond
wastrue
then route activation would work fine. Later ifcond
becomesfalse
, the outlet would be destroyed and children information stored on the directive (in theRouterOutletMap
) would be destroyed. Ifcond
becometrue
again the outlet would be empty because that info has been lost.closes #16423
TODO