ChatGPT解决这个技术问题 Extra ChatGPT

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

我想在用户离开我的 Angular 2 应用程序的特定页面之前警告用户未保存的更改。通常我会使用 window.onbeforeunload,但这不适用于单页应用程序。

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

我希望其他人已经找到了解决这个问题的方法;任何一种方法都可以很好地满足我的目的。

当您尝试关闭页面/选项卡时,它确实适用于单页应用程序。因此,如果他们忽略这一事实,对这个问题的任何答案都只是部分解决方案。

C
CularBytes

为了还包括防止浏览器刷新、关闭窗口等(有关该问题的详细信息,请参阅@ChristopheVidal 对 Günter 的回答的评论),我发现将 @HostListener 装饰器添加到您的类的 canDeactivate 实现以进行收听很有帮助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 {}

注意:正如@JasperRisseeuw 所指出的,IE 和 Edge 处理 beforeunload 事件的方式与其他浏览器不同,当 beforeunload 事件激活时(例如,浏览器刷新、关闭窗口等)。在 Angular 应用程序中导航不受影响,并且会正确显示您指定的确认警告消息。那些需要支持 IE/Edge 并且不希望 falsebeforeunload 事件激活时在确认对话框中显示/想要更详细的消息的人可能还希望查看@JasperRisseeuw 的解决方法的答案。


这真的很好@stewdebaker!我对此解决方案有一个补充,请参阅下面的答案。
从 'rxjs/Observable' 导入 { Observable }; ComponentCanDeactivate 中缺少
我必须将 @Injectable() 添加到 PendingChangesGuard 类。此外,我必须在 @NgModule 中将 PendingChangesGuard 添加到我的提供程序
值得注意的是,您必须返回一个布尔值,以防您被 beforeunload 导航。如果你返回一个 Observable,它将不起作用。您可能希望将界面更改为 canDeactivate: (internalNavigation: true | undefined) 之类的内容,并像这样调用您的组件:return component.canDeactivate(true)。这样,您可以检查您是否没有在内部导航以返回 false 而不是 Observable。
我做了上述所有事情,但它仅适用于路线更改,但不适用于浏览器事件 window:beforeunload。仅当用户尝试关闭或刷新浏览器时,如何才能使这项工作正常工作?
S
Stephen Turner

路由器提供生命周期回调 CanDeactivate

有关详细信息,请参阅 guards tutorial

类 UserToken {} 类权限 { canActivate(user: UserToken, id: string): boolean { return true; } } @Injectable() 类 CanActivateTeam 实现 CanActivate { 构造函数(私有权限:Permissions,私有 currentUser:UserToken){} canActivate(路由:ActivatedRouteSnapshot,状态:RouterStateSnapshot):Observable|Promise|boolean { return this .permissions.canActivate(this.currentUser, route.params.id); } } @NgModule({ 导入:[RouterModule.forRoot([ { path: 'team/:id', component: TeamCmp, canActivate: [CanActivateTeam] } ]) ],提供者:[CanActivateTeam, UserToken, Permissions] }) 类应用模块 {}

原装(RC.x 路由器)

类 CanActivateTeam 实现 CanActivate { 构造函数(私有权限:Permissions,私有 currentUser:UserToken){} canActivate(路由:ActivatedRouteSnapshot,状态:RouterStateSnapshot):Observable { return this.permissions.canActivate(this.currentUser, this.route.参数.id); } } bootstrap(AppComponent, [ CanActivateTeam, provideRouter([{ path: 'team/:id', component: Team, canActivate: [CanActivateTeam] }]) );


与 OP 所要求的不同,CanDeactivate 目前没有挂钩 onbeforeunload 事件(不幸的是)。这意味着如果用户尝试导航到外部 URL、关闭窗口等,将不会触发 CanDeactivate。它似乎仅在用户停留在应用程序内时才有效。
@ChristopheVidal 是正确的。请参阅我的答案以获取还包括导航到外部 URL、关闭窗口、重新加载页面等的解决方案。
这在更改路线时有效。如果是SPA呢?还有其他方法可以实现这一目标吗?
stackoverflow.com/questions/36763141/… 您也需要路线。如果窗口关闭或导航离开当前站点 canDeactivate 将不起作用。
J
Jasper Risseeuw

来自stewdebaker 的@Hostlistener 示例运行良好,但我对其进行了另一处更改,因为IE 和Edge 向最终用户显示了MyComponent 类的canDeactivate() 方法返回的“false”。

零件:

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)";
    }
  }
}

好收获@JasperRisseeuw!我没有意识到 IE/Edge 以不同的方式处理这个问题。对于需要支持 IE/Edge 并且不希望 false 在确认对话框中显示的用户来说,这是一个非常有用的解决方案。我对您的回答做了一个小的修改,将 '$event' 包含在 @HostListener 注释中,因为这是在 unloadNotification 函数中访问它所必需的。
谢谢,我忘了从我自己的代码中复制“,['$event']”,你也很好!
唯一可行的解决方案是这个(使用Edge)。所有其他的作品,但只显示默认对话框消息(Chrome/Firefox),而不是我的文字......我什至asked a question了解发生了什么
@ElmerDantas 请参阅您的问题的 my answer,了解 Chrome/Firefox 中显示默认对话框消息的原因。
实际上,它有效,对不起!我必须在模块提供程序中引用警卫。
S
Stephen Paul

2020年6月答案:

请注意,到目前为止提出的所有解决方案都没有处理 Angular 的 canDeactivate 防护的重大已知缺陷:

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

这已在 herehere 和详细here中讨论过

请参阅我对问题 demonstrated here 的解决方案,该解决方案可以安全地解决此问题*。这已经在 Chrome、Firefox 和 Edge 上进行了测试。

* IMPORTANT CAVEAT:在这个阶段,上面会在点击后退按钮时清除前进历史,但保留后退历史。如果保留您的转发历史记录至关重要,则此解决方案将不合适。在我的例子中,当涉及到表单时,我通常使用 master-detail 路由策略,因此维护转发历史记录并不重要。


目前从 ng 12.1.x 开始,路由器 angular.io/api/router/ExtraOptions#canceledNavigationResolution 中有一个选项可以让我们摆脱这种黑客攻击。
y
yankee

我已经实现了来自@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;
    })
  }
}

确认.component.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>

确认.component.ts

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 的情况下显示,因此需要在根 app.module.ts(或无论你命名你的根模块)。对 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

是否有机会显示浏览器刷新的自定义模型?
一定有办法,虽然这个解决方案可以满足我的需要。如果我有时间的话,我会进一步发展,尽管很长一段时间内我无法更新这个答案,抱歉!
B
Byron Lopez

该解决方案比预期的要容易,不要使用 href,因为这不是由 Angular Routing 使用 routerLink 指令处理的。


关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅