离开页面前警告用户未保存的更改

我想警告用户的未保存的变化之前,他们离开我的角度2应用程序的特定页面。通常我会使用 window.onbeforeunload,但是这对单页应用程序不起作用。

我发现在角度1中,你可以连接到 $locationChangeStart事件,为用户抛出一个 confirm框,但是我还没有看到任何东西显示如何让这个工作在角度2,或者如果这个事件仍然存在。我还看到了 ag1的 插件提供了 onbeforeunload的功能,但是,我还没有看到任何方法可以将其用于 ag2。

我希望其他人已经找到了这个问题的解决方案; 对于我的目的,这两种方法都可以很好地工作。

147032 次浏览

路由器提供了一个生命周期回调 可以停用

详情请参阅 警卫教程

class UserToken {}
class Permissions {
canActivate(user: UserToken, id: string): boolean {
return true;
}
}
@Injectable()
class CanActivateTeam implements CanActivate {
constructor(private permissions: Permissions, private currentUser: UserToken) {}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean>|Promise<boolean>|boolean {
return this.permissions.canActivate(this.currentUser, route.params.id);
}
}
@NgModule({
imports: [
RouterModule.forRoot([
{
path: 'team/:id',
component: TeamCmp,
canActivate: [CanActivateTeam]
}
])
],
providers: [CanActivateTeam, UserToken, Permissions]
})
class AppModule {}

原始(RC.x 路由器)

class CanActivateTeam implements CanActivate {
constructor(private permissions: Permissions, private currentUser: UserToken) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):Observable<boolean> {
return this.permissions.canActivate(this.currentUser, this.route.params.id);
}
}
bootstrap(AppComponent, [
CanActivateTeam,
provideRouter([{
path: 'team/:id',
component: Team,
canActivate: [CanActivateTeam]
}])
);

为了防止浏览器刷新,关闭窗口等等(详见@ChristopheVidal 对 Günter 回答的评论) ,我发现在你的类的 canDeactivate实现中添加 @HostListener装饰器来监听 beforeunload window事件是很有帮助的。如果配置正确,这将同时防止应用程序内导航和外部导航。

例如:

组成部分:

import { ComponentCanDeactivate } from './pending-changes.guard';
import { HostListener } from '@angular/core';
import { Observable } from 'rxjs/Observable';


export class MyComponent implements ComponentCanDeactivate {
// @HostListener allows us to also guard against browser refresh, close, etc.
@HostListener('window:beforeunload')
canDeactivate(): Observable<boolean> | boolean {
// insert logic to check if there are pending changes here;
// returning true will navigate without confirmation
// returning false will show a confirm dialog before navigating away
}
}

警卫:

import { CanDeactivate } from '@angular/router';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';


export interface ComponentCanDeactivate {
canDeactivate: () => boolean | Observable<boolean>;
}


@Injectable()
export class PendingChangesGuard implements CanDeactivate<ComponentCanDeactivate> {
canDeactivate(component: ComponentCanDeactivate): boolean | Observable<boolean> {
// if there are no pending changes, just allow deactivation; else confirm first
return component.canDeactivate() ?
true :
// NOTE: this warning message will only be shown when navigating elsewhere within your angular app;
// when navigating away from your angular app, the browser will show a generic warning message
// see http://stackoverflow.com/a/42207299/7307355
confirm('WARNING: You have unsaved changes. Press Cancel to go back and save these changes, or OK to lose these changes.');
}
}

路线:

import { PendingChangesGuard } from './pending-changes.guard';
import { MyComponent } from './my.component';
import { Routes } from '@angular/router';


export const MY_ROUTES: Routes = [
{ path: '', component: MyComponent, canDeactivate: [PendingChangesGuard] },
];

单元:

import { PendingChangesGuard } from './pending-changes.guard';
import { NgModule } from '@angular/core';


@NgModule({
// ...
providers: [PendingChangesGuard],
// ...
})
export class AppModule {}

注意 : 正如@JasperRisseuw 所指出的,IE 和 Edge 处理 beforeunload事件的方式与其他浏览器不同,当 beforeunload事件激活时,IE 和 Edge 会在确认对话框中包含单词 false(例如,刷新浏览器,关闭窗口等)。在角度应用程序导航远离是不受影响的,并会正确显示您指定的确认警告信息。那些需要支持 IE/Edge 并且不希望 false在确认对话框中显示/希望在 beforeunload事件激活时看到更详细消息的用户也可能希望看到@JasperRisseuw 的解决方案。

Stewdebaker 中@Hostlisten 的示例工作得非常好,但是我对它做了一个更改,因为 IE 和 Edge 向最终用户显示“ false”,这个“ false”由 MyComponent 类上的 canDeactive ()方法返回。

组成部分:

import {ComponentCanDeactivate} from "./pending-changes.guard";
import { Observable } from 'rxjs'; // add this line


export class MyComponent implements ComponentCanDeactivate {


canDeactivate(): Observable<boolean> | boolean {
// insert logic to check if there are pending changes here;
// returning true will navigate without confirmation
// returning false will show a confirm alert before navigating away
}


// @HostListener allows us to also guard against browser refresh, close, etc.
@HostListener('window:beforeunload', ['$event'])
unloadNotification($event: any) {
if (!this.canDeactivate()) {
$event.returnValue = "This message is displayed to the user in IE and Edge when they navigate without using Angular routing (type another URL/close the browser/etc)";
}
}
}

解决方案比预期的要简单,不要使用 href,因为这不是由角度路由使用 routerLink指令来处理的。

我已经从@stewdebaker 实现了这个解决方案,效果非常好,但是我想要一个漂亮的引导弹出窗口,而不是笨重的标准 JavaScript 确认。假设您已经在使用 ngx-bootstrap,那么可以使用@stwedebaker 的解决方案,但是将“ Guard”替换为我在这里展示的解决方案。您还需要引入 ngx-bootstrap/modal,并添加一个新的 ConfirmationComponent:

警卫

(用一个将打开引导模式(显示一个新的自定义 ConfirmationComponent)的函数替换‘启动’) :

import { Component, OnInit } from '@angular/core';
import { ConfirmationComponent } from './confirmation.component';


import { CanDeactivate } from '@angular/router';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { BsModalService } from 'ngx-bootstrap/modal';
import { BsModalRef } from 'ngx-bootstrap/modal';


export interface ComponentCanDeactivate {
canDeactivate: () => boolean | Observable<boolean>;
}


@Injectable()
export class PendingChangesGuard implements CanDeactivate<ComponentCanDeactivate> {


modalRef: BsModalRef;


constructor(private modalService: BsModalService) {};


canDeactivate(component: ComponentCanDeactivate): boolean | Observable<boolean> {
// if there are no pending changes, just allow deactivation; else confirm first
return component.canDeactivate() ?
true :
// NOTE: this warning message will only be shown when navigating elsewhere within your angular app;
// when navigating away from your angular app, the browser will show a generic warning message
// see http://stackoverflow.com/a/42207299/7307355
this.openConfirmDialog();
}


openConfirmDialog() {
this.modalRef = this.modalService.show(ConfirmationComponent);
return this.modalRef.content.onClose.map(result => {
return result;
})
}
}

确认. 组件. html

<div class="alert-box">
<div class="modal-header">
<h4 class="modal-title">Unsaved changes</h4>
</div>
<div class="modal-body">
Navigate away and lose them?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" (click)="onConfirm()">Yes</button>
<button type="button" class="btn btn-secondary" (click)="onCancel()">No</button>
</div>
</div>

确认,组件

import { Component } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { BsModalRef } from 'ngx-bootstrap/modal';


@Component({
templateUrl: './confirmation.component.html'
})
export class ConfirmationComponent {


public onClose: Subject<boolean>;


constructor(private _bsModalRef: BsModalRef) {


}


public ngOnInit(): void {
this.onClose = new Subject();
}


public onConfirm(): void {
this.onClose.next(true);
this._bsModalRef.hide();
}


public onCancel(): void {
this.onClose.next(false);
this._bsModalRef.hide();
}
}

由于新的 ConfirmationComponent将在 html 模板中显示,而不使用 selector,所以 它需要在 entryComponents中声明(不再需要艾薇了)位于根 app.module.ts(或任何您命名的根模块)中。对 app.module.ts进行以下修改:

应用程序模块

import { ModalModule } from 'ngx-bootstrap/modal';
import { ConfirmationComponent } from './confirmation.component';


@NgModule({
declarations: [
...
ConfirmationComponent
],
imports: [
...
ModalModule.forRoot()
],
entryComponents: [ConfirmationComponent] // Only when using old ViewEngine

2020年6月答案:

请注意,所有提出的解决方案,直到这一点不处理一个重大的已知缺陷与 Angular 的 canDeactivate后卫:

  1. 用户单击浏览器中的“后退”按钮,显示对话框,然后用户单击 取消
  2. 用户再次单击“后退”按钮,显示对话框,然后用户单击 确认
  3. 注意: 用户被导航回来2次,这甚至可以把他们从应用程序完全: (

这已经讨论了 给你给你给你


请参阅我的解决方案的问题 在这里展示的安全工程围绕这个问题 * 。这已经在 Chrome、 Firefox 和 Edge 上进行了测试。


* 重要警告: 在这个阶段,当单击后退按钮时,上面的代码将清除前进历史记录,但保留后退历史记录。如果保存您的前进历史是至关重要的,那么这个解决方案就不合适。在我的例子中,当涉及到表单时,我通常使用 细节大师路由策略,因此维护转发历史记录并不重要。