page change angular angular2-routing

angular - change - Advertir al usuario sobre cambios no guardados antes de abandonar la página



page title angular 4 (5)

El ejemplo con @Hostlistener de stewdebaker funciona muy bien, pero hice un cambio más porque IE y Edge muestran el "falso" que el método canDeactivate () devuelve en la clase MyComponent al usuario final.

Componente:

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

Me gustaría advertir a los usuarios de los cambios no guardados antes de que abandonen una página en particular de mi aplicación angular 2. Normalmente usaría window.onbeforeunload , pero eso no funciona para aplicaciones de una sola página.

Descubrí que en el ángulo 1, puede conectarse al evento $locationChangeStart para lanzar un cuadro de confirm para el usuario, pero no he visto nada que muestre cómo hacer que esto funcione para el angular 2, o si ese evento es Incluso todavía presente. También he visto plugins para ag1 que proporcionan funcionalidad para onbeforeunload , pero de nuevo, no he visto ninguna forma de usarlo para ag2.

Espero que alguien más haya encontrado una solución a este problema; cualquiera de los métodos funcionará bien para mis propósitos.


El enrutador proporciona una devolución de llamada del ciclo de vida CanDeactivate

para más detalles ver el tutorial de guardias

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 {}

original (enrutador 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] }]) );


Implementé la solución de @stewdebaker que funciona muy bien, sin embargo, quería una buena ventana emergente de arranque en lugar de la confirmación de JavaScript estándar torpe. Suponiendo que ya está usando ngx-bootstrap, puede usar la solución de @ stwedebaker, pero cambie la ''Guardia'' por la que estoy mostrando aquí. También debe introducir ngx-bootstrap/modal y agregar un nuevo ConfirmationComponent :

Guardia

(reemplace ''confirmar'' con una función que abrirá un modo de arranque, mostrando un nuevo ConfirmationComponent personalizado):

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://.com/a/42207299/7307355 this.openConfirmDialog(); } openConfirmDialog() { this.modalRef = this.modalService.show(ConfirmationComponent); return this.modalRef.content.onClose.map(result => { return result; }) } }

confirmacion.componente.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>

confirm.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(); } }

Y dado que el nuevo ConfirmationComponent se mostrará sin usar un selector en una plantilla html, debe declararse en entryComponents en su app.module.ts (o como quiera que nombre su módulo raíz). Realice los siguientes cambios en 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]


La solución fue más fácil de lo esperado, no use href porque Angular Routing no maneja la directiva routerLink .


Para también proteger a los guardias contra las actualizaciones del navegador, cerrar la ventana, etc. (vea el comentario de @ ChristopheVidal a la respuesta de Günter para obtener detalles sobre el tema), me ha resultado útil agregar el decorador @HostListener a la implementación de canDeactivate su clase para escuchar la beforeunload evento de window Cuando se configura correctamente, esto protegerá contra la navegación interna y externa al mismo tiempo.

Por ejemplo:

Componente:

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 } }

Guardia:

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://.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.''); } }

Rutas

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] }, ];

Módulo:

import { PendingChangesGuard } from ''./pending-changes.guard''; import { NgModule } from ''@angular/core''; @NgModule({ // ... providers: [PendingChangesGuard], // ... }) export class AppModule {}

NOTA : Como señaló @JasperRisseeuw, IE y Edge manejan el evento beforeunload diferente a otros navegadores e incluirán la palabra false en el diálogo de confirmación cuando se beforeunload evento beforeunload (por ejemplo, actualizaciones del navegador, cierre de la ventana, etc.). Navegar fuera de la aplicación Angular no se ve afectado y mostrará correctamente el mensaje de advertencia de confirmación designado. Aquellos que necesitan admitir IE / Edge y no quieren que se muestre false / quieren un mensaje más detallado en el cuadro de diálogo de confirmación cuando se activa el evento beforeunload también pueden querer ver la respuesta de @ JasperRisseeuw para una solución alternativa.