Предупреждать пользователя о несохраненных изменениях перед уходом со страницы
Я хотел бы предупредить пользователей о несохраненных изменениях, прежде чем они покинут определенную страницу моего приложения angular 2. Обычно я бы использовал window.onbeforeunload, но это не работает для одностраничных приложений.
я обнаружил, что в угловой 1, Вы можете подключить в $locationChangeStart событие, чтобы бросить вверх confirm box для пользователя, но я не видел ничего, что показывает, как заставить это работать для angular 2, или если это событие все еще присутствует. Я тоже видел Плагины для ag1 это обеспечить функциональность для onbeforeunload, но опять же, я не видел никакого способа использовать его для ag2.
Я надеюсь, что кто-то нашел решение этой проблемы; либо метод будет работать для моих целей.
5 ответов:
маршрутизатор обеспечивает обратный вызов жизненного цикла 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 {}оригинал (RC.х маршрутизатор)
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 к ответу Гюнтера для получения подробной информации по этому вопросу), я нашел полезным добавить
@HostListenerдекоратор для вашего классаcanDeactivateреализация для прослушиванияbeforeunloadwindowсобытие. При правильной настройке это будет защищать как от встроенной, так и от внешней навигации одновременно.для пример:
компоненты:
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событие отличается от других браузеров и будет включать в себя словоfalseв диалоговом окне подтверждения, когдаbeforeunloadактивация события (например, обновление браузера, закрыв окно и т. д.). Перемещение в пределах углового приложения не влияет и будет правильно отображать назначенное сообщение с предупреждением о подтверждении. Те, кому нужно поддерживать IE / Edge и не хотятfalseчтобы показать / хотите более подробное сообщение в диалоговом окне подтверждения, когдаbeforeunloadактивация события также может потребоваться увидеть ответ @JasperRisseeuw для обхода.
пример с @Hostlistener из stewdebaker работает очень хорошо, но я сделал еще одно изменение, потому что IE и Edge отображают "false", который возвращается методом canDeactivate() в классе MyComponent конечному пользователю.
компоненты:
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. Предполагая, что вы уже используете NGX-bootstrap, вы можете использовать решение @stwedebaker, но замените "Guard" на тот, который я показываю здесь. Вам также нужно ввести
ngx-bootstrap/modalи добавить новыйConfirmationComponent:гвардии
(замените 'confirm' функцией, которая откроет модальный загрузчик-отображение a новые, изготовленные на заказ
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будет отображаться без использованияselectorв шаблоне html, он должен быть объявлен в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]
решение было проще, чем ожидалось, не используйте
hrefпотому что это не обрабатывается с помощью угловой маршрутизацииrouterLinkвместо директивы.
Comments