fix(router-outlet): attach entering view before first change detection (#18821)

This commit is contained in:
Manu MA
2019-07-18 10:26:54 +02:00
committed by GitHub
parent 26e6d6f115
commit 97fec92365
10 changed files with 112 additions and 63 deletions

View File

@ -205,7 +205,6 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
// Calling `markForCheck` to make sure we will run the change detection when the
// `RouterOutlet` is inside a `ChangeDetectionStrategy.OnPush` component.
enteringView = this.stackCtrl.createView(this.activated, activatedRoute);
enteringView.ref.changeDetectorRef.detectChanges();
// Store references to the proxy by component
this.proxyMap.set(cmpRef.instance, activatedRouteProxy);

View File

@ -31,7 +31,7 @@ export class StackController {
createView(ref: ComponentRef<any>, activatedRoute: ActivatedRoute): RouteView {
const url = getUrl(this.router, activatedRoute);
const element = (ref && ref.location && ref.location.nativeElement) as HTMLElement;
const unlistenEvents = bindLifecycleEvents(ref.instance, element);
const unlistenEvents = bindLifecycleEvents(this.zone, ref.instance, element);
return {
id: this.nextId++,
stackId: computeStackId(this.tabsPrefix, url),
@ -90,16 +90,28 @@ export class StackController {
}
}
const reused = this.views.includes(enteringView);
const views = this.insertView(enteringView, direction);
return this.wait(() => {
return this.transition(enteringView, leavingView, animation, this.canGoBack(1), false)
.then(() => cleanupAsync(enteringView, views, viewsSnapshot, this.location))
.then(() => ({
enteringView,
direction,
animation,
tabSwitch
}));
// Trigger change detection before transition starts
// This will call ngOnInit() the first time too, just after the view
// was attached to the dom, but BEFORE the transition starts
if (!reused) {
enteringView.ref.changeDetectorRef.detectChanges();
}
// Wait until previous transitions finish
return this.zone.runOutsideAngular(() => {
return this.wait(() => {
return this.transition(enteringView, leavingView, animation, this.canGoBack(1), false)
.then(() => cleanupAsync(enteringView, views, viewsSnapshot, this.location))
.then(() => ({
enteringView,
direction,
animation,
tabSwitch
}));
});
});
}
@ -199,11 +211,11 @@ export class StackController {
if (enteringView) {
enteringView.ref.changeDetectorRef.reattach();
}
// TODO: disconnect leaving page from change detection to
// disconnect leaving page from change detection to
// reduce jank during the page transition
// if (leavingView) {
// leavingView.ref.changeDetectorRef.detach();
// }
if (leavingView) {
leavingView.ref.changeDetectorRef.detach();
}
const enteringEl = enteringView ? enteringView.element : undefined;
const leavingEl = leavingView ? leavingView.element : undefined;
const containerEl = this.containerEl;
@ -213,13 +225,13 @@ export class StackController {
containerEl.appendChild(enteringEl);
}
return this.zone.runOutsideAngular(() => containerEl.commit(enteringEl, leavingEl, {
return containerEl.commit(enteringEl, leavingEl, {
deepWait: true,
duration: direction === undefined ? 0 : undefined,
direction,
showGoBack,
progressAnimation
}));
});
}
return Promise.resolve(false);
}

View File

@ -34,10 +34,10 @@ export class AngularFrameworkDelegate implements FrameworkDelegate {
) {}
attachViewToDom(container: any, component: any, params?: any, cssClasses?: string[]): Promise<any> {
return new Promise(resolve => {
this.zone.run(() => {
return this.zone.run(() => {
return new Promise(resolve => {
const el = attachView(
this.resolver, this.injector, this.location, this.appRef,
this.zone, this.resolver, this.injector, this.location, this.appRef,
this.elRefMap, this.elEventsMap,
container, component, params, cssClasses
);
@ -47,8 +47,8 @@ export class AngularFrameworkDelegate implements FrameworkDelegate {
}
removeViewFromDom(_container: any, component: any): Promise<void> {
return new Promise(resolve => {
this.zone.run(() => {
return this.zone.run(() => {
return new Promise(resolve => {
const componentRef = this.elRefMap.get(component);
if (componentRef) {
componentRef.destroy();
@ -66,6 +66,7 @@ export class AngularFrameworkDelegate implements FrameworkDelegate {
}
export function attachView(
zone: NgZone,
resolver: ComponentFactoryResolver,
injector: Injector,
location: ViewContainerRef | undefined,
@ -93,7 +94,7 @@ export function attachView(
hostElement.classList.add(clazz);
}
}
const unbindEvents = bindLifecycleEvents(instance, hostElement);
const unbindEvents = bindLifecycleEvents(zone, instance, hostElement);
container.appendChild(hostElement);
if (!location) {
@ -113,15 +114,17 @@ const LIFECYCLES = [
LIFECYCLE_WILL_UNLOAD
];
export function bindLifecycleEvents(instance: any, element: HTMLElement) {
const unregisters = LIFECYCLES
.filter(eventName => typeof instance[eventName] === 'function')
.map(eventName => {
const handler = (ev: any) => instance[eventName](ev.detail);
element.addEventListener(eventName, handler);
return () => element.removeEventListener(eventName, handler);
});
return () => unregisters.forEach(fn => fn());
export function bindLifecycleEvents(zone: NgZone, instance: any, element: HTMLElement) {
return zone.run(() => {
const unregisters = LIFECYCLES
.filter(eventName => typeof instance[eventName] === 'function')
.map(eventName => {
const handler = (ev: any) => instance[eventName](ev.detail);
element.addEventListener(eventName, handler);
return () => element.removeEventListener(eventName, handler);
});
return () => unregisters.forEach(fn => fn());
});
}
const NavParamsToken = new InjectionToken<any>('NavParamsToken');