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

Whe*_*lch 93 angular2-routing angular

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

我发现在角度1中,你可以挂钩$locationChangeStart事件confirm为用户抛出一个盒子,但我还没有看到任何显示如何使角度2工作,或者如果该事件仍然存在.我也见过为ag1提供功能的插件onbeforeunload,但同样,我还没有看到任何方法将它用于ag2.

我希望其他人找到解决这个问题的方法; 任何一种方法都可以用于我的目的.

ste*_*ker 184

为了防止浏览器刷新,关闭窗口等(请参阅@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
  }
}
Run Code Online (Sandbox Code Playgroud)

守护:

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.');
  }
}
Run Code Online (Sandbox Code Playgroud)

路线:

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] },
];
Run Code Online (Sandbox Code Playgroud)

模块:

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

@NgModule({
  // ...
  providers: [PendingChangesGuard],
  // ...
})
export class AppModule {}
Run Code Online (Sandbox Code Playgroud)

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

  • 这真的很好@stewdebaker!我对此解决方案有一个补充,请参阅下面的答案. (2认同)
  • 我必须将 `@Injectable()` 添加到 PendingChangesGuard 类中。另外,我必须将 PendingChangesGuard 添加到“@NgModule”中的提供程序 (2认同)
  • 值得注意的是,如果您被`beforeunload`导航,则必须返回一个布尔值。如果返回一个Observable,它将无法正常工作。您可能需要将接口更改为诸如`canDeactivate:(internalNavigation:true | undefined)`之类,并以如下方式调用组件:`return component.canDeactivate(true)`。这样,您可以检查是否未在内部进行导航以返回false而不是Observable。 (2认同)
  • 我做了上面的一切,但它只适用于路由更改,但不适用于浏览器事件“window:beforeunload”。仅当用户尝试关闭或刷新浏览器时,如何才能使其工作? (2认同)

Gün*_*uer 63

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

有关详细信息,请参阅警卫教程

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 {}
Run Code Online (Sandbox Code Playgroud)

原始(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]
  }])
);
Run Code Online (Sandbox Code Playgroud)

  • 与OP要求的不同,CanDeactivate当前没有挂钩onbeforeunload事件(不幸的是).这意味着如果用户尝试导航到外部URL,请关闭窗口等.不会触发CanDeactivate.它似乎仅在用户留在应用程序中时才有效. (22认同)
  • @ChristopheVidal 是正确的。请参阅我的解决方案的答案,该解决方案还包括导航到外部 URL、关闭窗口、重新加载页面等。 (3认同)

Jas*_*euw 54

来自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)";
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 好抓@JasperRisseeuw!我没有意识到IE/Edge处理的方式不同.对于那些需要支持IE/Edge并且不希望在"确认"对话框中显示"false"的人来说,这是一个非常有用的解决方案.我对你的答案做了一个小编辑,在`@HostListener`注释中包含`'$ event'`,因为这是为了能够在`unloadNotification`函数中访问它. (2认同)
  • 实际上,它有效,对不起!我不得不在模块提供商中引用警卫. (2认同)

Ste*_*aul 14

2020 年 6 月答案:

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

  1. 用户单击浏览器中的“后退”按钮,显示对话框,然后用户单击CANCEL
  2. 用户再次单击“后退”按钮,显示对话框,然后用户单击CONFIRM
  3. 注意:用户被导航回 2 次,这甚至可能将他们完全带出应用程序 :(

这已经讨论过这里在这里,并在长度这里


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


* IMPORTANT CAVEAT : 在这个阶段,当点击后退按钮时,上面将清除前进历史,但保留后退历史。如果保留您的前进历史至关重要,则此解决方案将不合适。就我而言,我通常在处理表单时使用主从路由策略,因此维护前向历史记录并不重要。


Chr*_*row 13

我已经实现了来自@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;
    })
  }
}
Run Code Online (Sandbox Code Playgroud)

确认.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>
Run Code Online (Sandbox Code Playgroud)

确认.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();
    }
}
Run Code Online (Sandbox Code Playgroud)

并且由于 newConfirmationComponentselector在 html 模板中不使用 a 显示,因此需要entryComponents在您的根(或您命名根模块的任何名称)中声明不再需要 Ivyapp.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
Run Code Online (Sandbox Code Playgroud)


Kir*_*ohn 9

对于 Angular 15,基于类的路由防护已被弃用,并被基于函数的路由防护取代。有关更多详细信息,请参阅此链接

我采用了@stewdebaker 的优秀解决方案并进行了必要的更改。唯一的变化是守卫本身,并且您不需要任何模块更新。

组件(与@stewdebaker 相比没有变化)

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
  }
}
Run Code Online (Sandbox Code Playgroud)

警卫

import { CanDeactivateFn, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';

// Full solution found here: /sf/answers/2883154361/
// and then changed to use the function-based method of doing route guards
// Updated solution found here: /sf/answers/5303837311/

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

export const PendingChangesGuard: CanDeactivateFn<ComponentCanDeactivate> = (
  component: ComponentCanDeactivate
): Observable<boolean | UrlTree> => {
  return new Observable<boolean | UrlTree>((obs) => {
    // if there are no pending changes, just allow deactivation; else confirm first
    return component.canDeactivate()
      ? obs.next(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
        obs.next(
          confirm(
            'WARNING: You have unsaved changes. Press Cancel to go back and save these changes, or OK to lose these changes.'
          )
        );
  });
};
Run Code Online (Sandbox Code Playgroud)

路线(与@stewdebaker 相比没有变化)

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] },
];
Run Code Online (Sandbox Code Playgroud)

模块

(基于功能的路由防护不需要更改模块)


归档时间:

查看次数:

59951 次

最近记录:

6 年,7 月 前