Skip to content

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

Merged
merged 2 commits into from
May 18, 2017

Conversation

vicb
Copy link
Contributor

@vicb vicb commented May 3, 2017

Problem

Before this PR, the information about the child outlet were stored in a RouterOutletMap on the parent RouterOutlet directive.
This means that all the RouterOutlets 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 nested RouterOutlet.

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 the RouterOutlet.

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 its ngOnInit 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:

<ng-container *ngIf="cond">
  <router-outlet></router-outlet>
</ng-container>

Before:

  • if cond was false while the route is activated this would be an error as the outlet would not exist when the route is activated,
  • if cond was true then route activation would work fine. Later if cond becomes false, the outlet would be destroyed and children information stored on the directive (in the RouterOutletMap) would be destroyed. If cond become true again the outlet would be empty because that info has been lost.

closes #16423

TODO

  • add more tests for the attach/detach scenario
  • validate with animation
  • validate within g3
  • discuss about breaking changes

@vicb vicb added area: router action: review The PR is still awaiting reviews from at least one requested reviewer labels May 3, 2017
@vicb vicb requested a review from mhevery May 3, 2017 15:27
@vicb vicb force-pushed the 0503-router branch 2 times, most recently from 2cc5f42 to 6a57a24 Compare May 4, 2017 15:14
@vicb vicb changed the title refactor(router): misc [WIP] refactor(router): remove extra CD run on activation May 4, 2017
@vicb vicb added state: WIP and removed action: review The PR is still awaiting reviews from at least one requested reviewer labels May 4, 2017
@vicb vicb force-pushed the 0503-router branch 3 times, most recently from 5cd66ea to cf6d4a1 Compare May 5, 2017 13:02
Copy link
Contributor

@vsavkin vsavkin left a 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);
Copy link
Contributor

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

Copy link
Contributor Author

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) {
Copy link
Contributor

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?

Copy link
Contributor Author

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 !; }
Copy link
Contributor

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 {
Copy link
Contributor

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.

Copy link
Contributor Author

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) {
Copy link
Contributor

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 {
Copy link
Contributor

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) {
Copy link
Contributor

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.


// 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);
Copy link
Contributor

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?

@vicb vicb force-pushed the 0503-router branch 2 times, most recently from 1f831ff to e660488 Compare May 10, 2017 14:06
@vicb vicb changed the title [WIP] refactor(router): remove extra CD run on activation refactor(router): remove extra CD run on activation May 15, 2017
@mary-poppins
Copy link

The angular.io preview for 27f1628 is available here.

@vicb vicb added action: review The PR is still awaiting reviews from at least one requested reviewer and removed state: WIP labels May 17, 2017
@mary-poppins
Copy link

The angular.io preview for 62b0014 is available here.

@mary-poppins
Copy link

The angular.io preview for 2135bcb is available here.

@mary-poppins
Copy link

The angular.io preview for 8d3e6f4 is available here.

@matsko matsko merged commit 198edb3 into angular:master May 18, 2017
@vicb vicb deleted the 0503-router branch May 19, 2017 17:26
sis0k0 added a commit to NativeScript/nativescript-angular that referenced this pull request Jun 27, 2017
- don't run change detection every time an outlet is activated
- drop `routerOutletMap`s

caused by angular/angular#16510
sis0k0 added a commit to NativeScript/nativescript-angular that referenced this pull request Jul 7, 2017
- don't run change detection every time an outlet is activated
- drop `routerOutletMap`s

caused by angular/angular#16510
sis0k0 added a commit to NativeScript/nativescript-angular that referenced this pull request Jul 11, 2017
- don't run change detection every time an outlet is activated
- drop `routerOutletMap`s

caused by angular/angular#16510
sis0k0 added a commit to NativeScript/nativescript-angular that referenced this pull request Jul 13, 2017
- don't run change detection every time an outlet is activated
- drop `routerOutletMap`s

caused by angular/angular#16510
sis0k0 added a commit to NativeScript/nativescript-angular that referenced this pull request Jul 20, 2017
- don't run change detection every time an outlet is activated
- drop `routerOutletMap`s

caused by angular/angular#16510
@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 11, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: review The PR is still awaiting reviews from at least one requested reviewer area: router cla: yes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants