ユーザーがangular 2アプリの特定のページを離れる前に、未保存の変更についてユーザーに警告したいと思います。通常、window.onbeforeunload
を使用しますが、単一ページのアプリケーションでは機能しません。
angular 1では、$locationChangeStart
イベントにフックしてユーザー用のconfirm
ボックスを表示できることがわかりましたが、angular 2、またはそのイベントがまだ存在する場合。また、onbeforeunload
の機能を提供するag1の plugins を確認しましたが、ag2に使用する方法はまだありません。
他の誰かがこの問題の解決策を見つけてくれることを望んでいます。どちらの方法でも、私の目的には適しています。
ルーターはライフサイクルコールバックを提供します CanDeactivate
詳細については、 guards tutorial をご覧ください。
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] }]) );
ブラウザの更新、ウィンドウのクローズなどに対するガードもカバーするために(問題の詳細については、Günterの回答に対する@ChristopheVidalのコメントを参照)、クラスの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 {}
NOTE:@JasperRisseeuwが指摘したように、IEとEdgeはbeforeunload
イベントを他のブラウザーとは異なる方法で処理し、false
イベントがアクティブになると、確認ダイアログにWord beforeunload
を含めます(たとえば、ブラウザの更新、ウィンドウのクローズなど)。 Angularアプリ内を移動しても影響はなく、指定した確認警告メッセージが適切に表示されます。 IE/Edgeをサポートする必要があり、false
イベントがアクティブになったときに、beforeunload
が確認ダイアログに詳細なメッセージを表示/表示したくない場合は、回避策として@JasperRisseeuwの回答を参照してください。
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)";
}
}
}
@stewdebakerのソリューションを実装しましたが、これは非常にうまく機能しますが、不格好な標準JavaScript確認の代わりにNice bootstrapポップアップが必要でした。すでにngx-bootstrapを使用していると仮定すると、@ stwedebakerのソリューションを使用できますが、ここで示しているものと「ガード」を交換します。また、ngx-bootstrap/modal
を導入し、新しいConfirmationComponent
を追加する必要があります。
( 'confirm'をbootstrapモーダルを開く関数に置き換えます-新しいカスタム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;
})
}
}
<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
を使用せずに表示されるため、ルートapp.module.ts
(またはルートモジュールに名前を付けるもの)のentryComponents
で宣言する必要があります。 app.module.ts
に次の変更を加えます。
import { ModalModule } from 'ngx-bootstrap/modal';
import { ConfirmationComponent } from './confirmation.component';
@NgModule({
declarations: [
...
ConfirmationComponent
],
imports: [
...
ModalModule.forRoot()
],
entryComponents: [ConfirmationComponent]
Angularルーティングでは処理されないため、href
を使用しないでください。代わりにrouterLink
ディレクティブを使用します。