mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-16 18:17:31 +08:00
fix(angular): race condition when fast navigation (#17197)
fixes #17194 fixes #16449 fixes #15413
This commit is contained in:
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user