fix(angular): race condition when fast navigation (#17197)

fixes #17194
fixes #16449
fixes #15413
This commit is contained in:
Manu MA
2019-01-22 15:03:43 +01:00
committed by GitHub
parent 3defbf3a8f
commit a945b03144
11 changed files with 165 additions and 56 deletions

View File

@ -9,9 +9,8 @@ import { RouteView, StackEvent, computeStackId, destroyView, getUrl, insertView,
export class StackController {
private viewsSnapshot: RouteView[] = [];
private views: RouteView[] = [];
private runningTransition?: Promise<boolean>;
private runningTask?: Promise<any>;
private skipTransition = false;
private tabsPrefix: string[] | undefined;
private activeView: RouteView | undefined;
@ -50,7 +49,7 @@ export class StackController {
return view;
}
async setActive(enteringView: RouteView): Promise<StackEvent> {
setActive(enteringView: RouteView): Promise<StackEvent> {
let { direction, animation } = this.navCtrl.consumeTransition();
const leavingView = this.activeView;
const tabSwitch = isTabSwitch(enteringView, leavingView);
@ -58,15 +57,18 @@ export class StackController {
direction = 'back';
animation = undefined;
}
this.insertView(enteringView, direction);
await this.transition(enteringView, leavingView, animation, this.canGoBack(1), false);
await this.cleanupAsync();
return {
enteringView,
direction,
animation,
tabSwitch
};
const viewsSnapshot = this.views.slice();
const views = this.insertView(enteringView, direction);
return this.wait(async () => {
await this.transition(enteringView, leavingView, animation, this.canGoBack(1), false);
await cleanupAsync(enteringView, views, viewsSnapshot);
return {
enteringView,
direction,
animation,
tabSwitch
};
});
}
canGoBack(deep: number, stackId = this.getActiveStackId()): boolean {
@ -84,19 +86,21 @@ export class StackController {
});
}
startBackTransition() {
async startBackTransition() {
const leavingView = this.activeView;
if (leavingView) {
const views = this.getStack(leavingView.stackId);
const enteringView = views[views.length - 2];
enteringView.ref.changeDetectorRef.reattach();
this.transition(
enteringView, // entering view
leavingView, // leaving view
'back',
true,
true
);
await this.wait(() => {
return this.transition(
enteringView, // entering view
leavingView, // leaving view
'back',
true,
true
);
});
}
}
@ -130,33 +134,7 @@ export class StackController {
private insertView(enteringView: RouteView, direction: RouterDirection) {
this.activeView = enteringView;
this.views = insertView(this.views, enteringView, direction);
}
private cleanupAsync() {
return new Promise(resolve => {
requestAnimationFrame(() => {
this.cleanup();
resolve();
});
});
}
private cleanup() {
const activeRoute = this.activeView;
const views = this.views;
this.viewsSnapshot
.filter(view => !views.includes(view))
.forEach(view => destroyView(view));
views.forEach(view => {
if (view !== activeRoute) {
const element = view.element;
element.setAttribute('aria-hidden', 'true');
element.classList.add('ion-page-hidden');
view.ref.changeDetectorRef.detach();
}
});
this.viewsSnapshot = views.slice();
return this.views.slice();
}
private async transition(
@ -166,10 +144,6 @@ export class StackController {
showGoBack: boolean,
progressAnimation: boolean
) {
if (this.runningTransition !== undefined) {
await this.runningTransition;
this.runningTransition = undefined;
}
if (this.skipTransition) {
this.skipTransition = false;
return;
@ -184,14 +158,46 @@ export class StackController {
}
await containerEl.componentOnReady();
this.runningTransition = containerEl.commit(enteringEl, leavingEl, {
await containerEl.commit(enteringEl, leavingEl, {
deepWait: true,
duration: direction === undefined ? 0 : undefined,
direction,
showGoBack,
progressAnimation
});
await this.runningTransition;
}
}
private async wait<T>(task: () => Promise<T>): Promise<T> {
if (this.runningTask !== undefined) {
await this.runningTask;
this.runningTask = undefined;
}
const promise = this.runningTask = task();
return promise;
}
}
function cleanupAsync(activeRoute: RouteView, views: RouteView[], viewsSnapshot: RouteView[]) {
return new Promise(resolve => {
requestAnimationFrame(() => {
cleanup(activeRoute, views, viewsSnapshot);
resolve();
});
});
}
function cleanup(activeRoute: RouteView, views: RouteView[], viewsSnapshot: RouteView[]) {
viewsSnapshot
.filter(view => !views.includes(view))
.forEach(destroyView);
views.forEach(view => {
if (view !== activeRoute) {
const element = view.element;
element.setAttribute('aria-hidden', 'true');
element.classList.add('ion-page-hidden');
view.ref.changeDetectorRef.detach();
}
});
}