Angular CanDeactivate Guard 无法与 MatDialog 一起正常工作

Angular CanDeactivate Guard Not Working Properly With MatDialog

我用 CanDeactivate 创建了一个路由守卫,它确定用户是否可以在有任何未保存的更改时离开页面,并在存在任何未保存的更改时触发 MatDialog 模式。我看过很多指南和类似的线程,我的代码正在运行(大部分)。见下文:

这是路由守卫,它调用组件的 confirmRouteChange 函数,如果它有一个,returns 结果,一个 Observable<boolean>:

@Injectable({
    providedIn: 'root'
})
export class UnsavedChangesGuard implements CanDeactivate<DirtyComponent> {
    canDeactivate(component: DirtyComponent): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
        return component?.confirmRouteChange ? component.confirmRouteChange() : true;
    }
}

这里是 confirmRouteChange 的实现,它只在表单脏时打开 MatDialog 并且 returns 在对话框关闭时通知的 Observable:

confirmRouteChange() {
        if (this.isDirty) {
            let dialogRef = this.matDialog.open(AlertModalComponent, {
                data: {
                    msg: 'Your changes have not been saved. If you leave this page, your changes will be lost.',
                    title: 'Changes have not been saved',
                    class: 'save-changes'
                }
            });

            return dialogRef.afterClosed();
        } else {
            return true;
        }
    }

下面是我的模式 save/close 选项的实现:

save() {
        this.dialogRef.close(false);
    }

    close() {
        this.dialogRef.close(true);
    }

所以这是我的问题:当我离开未保存的更改时,模式弹出(很好),应用程序根据在模式中选择的选项正确导航(很好);但是,在模态后面,我可以看到我的应用程序的其余部分离开了页面,除了路由守卫所在的组件。因此,如果我尝试导航到主页,我可以看到所有主页和未保存更改形式的组件。就好像它开始导航,停止,然后等待用户的响应。如果我使用本机 confirm() 对话框,它会完美运行,这意味着应用程序在等待用户响应时会有效地冻结。我相信这是因为如果我记录 confirm() returns,它不会 return 任何东西,直到用户选择某些东西。另一方面,dialogRef.afterClosed() return 立即触发 Observable,我猜这会触发导航,然后应用停止并等待用户的响应。

我认为我对模态的使用有问题,但我不确定是什么。非常感谢任何帮助!

编辑:我认为 return dialogRef.afterClosed() 语句在对话框完成打开之前执行,导致导航开始然后停止。

这正是你描述的方式。

你的路由守卫期望立即判断真或假。它无法处理可观察对象。

所以我的建议是执行以下操作。

第一

进行检查,但直接 return false,当组件变脏时,路由守卫会阻止重新路由。构建一个服务,它存储新路由并包含一个名为 reroutingAllowed 的字段。这最初设置为 false.

canDeactivate(component: DirtyComponent): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    return component?.isDirty();
}

confirmRouteChange() {
    // here we already take the new field into account
    if (this.isDirty && !reroutingAllowed) {
        let dialogRef = this.matDialog.open(AlertModalComponent, {
            data: {
                msg: 'Your changes have not been saved. If you leave this page, your changes will be lost.',
                title: 'Changes have not been saved',
                class: 'save-changes'
            }
        });

        // directly return false
        return false;
    } else {
        return true;
    }
}

第二

并行存储用户希望在您的服务中到达的新路由目标,以便在用户在模式对话框中做出决定时访问它。

第三

等待用户回答对话。如果他想离开当前页面,将变量 reroutingAllowed 设置为 true 并触发 Angular 路由器再次调用新的路由目标。

这次守卫会让用户通过,尽管表单仍然很脏。

你只需要考虑如何设置提到的服务以及如何获取和记忆新的路由目标。

我遇到了完全同样的问题。我能够通过简单地将 observable 转换为 promise 来解决它。

而不是:

dialogRef.afterClosed()

这应该可以解决问题:

dialogRef.afterClosed().toPromise()