wake-up-neo.net

Benutzer vor nicht abgespeicherten Änderungen vor dem Verlassen der Seite warnen

Ich möchte Benutzer auf ungespeicherte Änderungen hinweisen, bevor sie eine bestimmte Seite meiner angle 2 App verlassen. Normalerweise würde ich window.onbeforeunload verwenden, aber das funktioniert nicht für Einzelseitenanwendungen.

Ich habe herausgefunden, dass Sie sich in Winkel 1 in das Ereignis $locationChangeStart einhaken können, um eine confirm-Box für den Benutzer zu aktivieren. Ich habe jedoch noch nichts gesehen, das zeigt, wie Sie dieses Verhalten für Winkel 2 ausführen können oder ob das Ereignis gerade ist immer noch anwesend Ich habe auch plugins für ag1 gesehen, die Funktionen für onbeforeunload bereitstellen, aber auch hier habe ich keine Möglichkeit gesehen, sie für ag2 zu verwenden.

Ich hoffe, dass jemand anderes eine Lösung für dieses Problem gefunden hat. Beide Methoden funktionieren gut für meine Zwecke.

75
Whelch

Der Router bietet einen Lebenszyklus-Rückruf CanDeactivate

weitere Informationen finden Sie im 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 {}

original (RC.x-Router)

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]
  }])
);
60

Um auch Schutzvorrichtungen gegen Browser-Aktualisierungen, das Schließen des Fensters usw. abzudecken (siehe Kommentar von ChristopheVidal zu Günters Antwort), finde ich es hilfreich, den @HostListener-Dekorator zur canDeactivate-Implementierung Ihrer Klasse hinzuzufügen, um auf die beforeunloadwindow zu hören. Veranstaltung. Bei korrekter Konfiguration schützt dies gleichzeitig vor In-App- und externer Navigation.

Zum Beispiel:

Komponente:

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

Wache:

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

Routen:

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

Modul:

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

@NgModule({
  // ...
  providers: [PendingChangesGuard],
  // ...
})
export class AppModule {}

NOTE: Wie @JasperRisseeuw darauf hinweist, behandeln IE und Edge das Ereignis beforeunload anders als andere Browser und fügen das Wort false in das Bestätigungsdialogfeld ein, wenn das Ereignis beforeunload aktiviert wird (z. B. Schließen des Fensters usw.). Das Navigieren in der Angular-App ist davon nicht betroffen und zeigt die entsprechende Bestätigungswarnmeldung ordnungsgemäß an. Diejenigen, die IE/Edge unterstützen müssen und nicht möchten, dass false eine ausführlichere Meldung im Bestätigungsdialogfeld anzeigt, wenn das beforeunload-Ereignis aktiviert wird, möchten möglicherweise auch die Antwort von @ JasperRisseeuw für eine Problemumgehung.

152
stewdebaker

Das Beispiel mit dem @Hostlistener von stewdebaker funktioniert wirklich gut, aber ich habe noch eine weitere Änderung vorgenommen, da IE und Edge das "false" anzeigen, das von der canDeactivate () - Methode der MyComponent-Klasse an den Endbenutzer zurückgegeben wird .

Komponente:

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

Ich habe die Lösung von @stewdebaker implementiert, die wirklich gut funktioniert, allerdings wollte ich ein Nice-Bootstrap-Popup anstelle des klobigen Standard-JavaScripts. Vorausgesetzt, Sie verwenden bereits ngx-bootstrap, können Sie die Lösung von @ stwedebaker verwenden, aber tauschen Sie den 'Guard' gegen den hier gezeigten aus. Sie müssen auch ngx-bootstrap/modal einführen und eine neue ConfirmationComponent hinzufügen:

Bewachen

(Ersetzen Sie 'confirm' durch eine Funktion, die ein Bootstrap-Modal öffnet - und zeigt eine neue, benutzerdefinierte 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;
    })
  }
}

confirmation.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>

bestätigung.Komponente.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();
    }
}

Und da die neue Variable ConfirmationComponent ohne Verwendung einer Variable selector in einer HTML-Vorlage angezeigt wird, muss sie in entryComponents in Ihrem Stammcode app.module.ts (oder wie auch immer Sie Ihr Stammmodul benennen) deklariert werden. Nehmen Sie folgende Änderungen an app.module.ts vor:

app.module.ts

import { ModalModule } from 'ngx-bootstrap/modal';
import { ConfirmationComponent } from './confirmation.component';

@NgModule({
  declarations: [
     ...
     ConfirmationComponent
  ],
  imports: [
     ...
     ModalModule.forRoot()
  ],
  entryComponents: [ConfirmationComponent]
2
Chris Halcrow

Die Lösung war einfacher als erwartet, verwenden Sie href nicht, da dies nicht von der Angular Routing-Anweisung routerLink ausgeführt wird.

0
Byron Lopez