2016-03-10 10 views
41

Desidero avvisare gli utenti delle modifiche non salvate prima che lascino una determinata pagina della mia app angolare 2. Normalmente userei window.onbeforeunload, ma questo non funziona per le applicazioni a pagina singola.Avvisa l'utente delle modifiche non salvate prima di uscire dalla pagina

Ho scoperto che in angolare 1, è possibile collegare in caso $locationChangeStart a vomitare una scatola confirm per l'utente, ma non ho visto nulla che mostra come ottenere questo lavoro per angolare 2, o se quell'evento è ancora presente. Ho anche visto plugins per ag1 che fornisce funzionalità per onbeforeunload, ma ancora una volta, non ho visto alcun modo di usarlo per ag2.

Spero che qualcun altro abbia trovato una soluzione a questo problema; entrambi i metodi funzioneranno bene per i miei scopi.

+1

Funziona per le applicazioni a pagina singola, quando si tenta di chiudere la pagina/scheda. Quindi qualsiasi risposta alla domanda sarebbe solo una soluzione parziale se ignorano questo fatto. –

risposta

37

Il router fornisce una funzione di callback del ciclo di vita [CanDeactivate] https://angular.io/docs/ts/latest/api/router/index/CanDeactivate-interface.html dove è possibile impedire percorso-cambiamento.

per maggiori dettagli si veda la (router RC.x) originale 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 {} 

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

Non sono sicuro di come non l'ho trovato. . . è esattamente _ quello che stavo cercando. Grazie! – Whelch

+0

Buono a sapersi e difficile da trovare. votazione +1 :-) – Marc

+14

A differenza di quanto richiesto dall'OP, CanDeactivate è - attualmente - non agganciato all'evento onbeforeunload (sfortunatamente). Ciò significa che se l'utente tenta di navigare verso un URL esterno, chiude la finestra, ecc. CanDeactivate non verrà attivato. Sembra funzionare solo quando l'utente si trova all'interno dell'app. –

83

Per coprire anche le guardie contro il browser si aggiorna, di chiusura la finestra, ecc. (vedi il commento di @ ChristopheVidal alla risposta di Günter per i dettagli sul problema), ho trovato utile aggiungere il decoratore @HostListener all'implementazione canDeactivate della tua classe per ascoltare l'evento beforeunloadwindow. Se configurato correttamente, questo proteggerà sia la navigazione in-app che quella esterna allo stesso tempo.

Ad esempio:

Componente:

import {ComponentCanDeactivate} from './pending-changes.guard'; 

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'; 

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

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

Rotte:

import {PendingChangesGuard} from './pending-changes.guard'; 

export const MY_ROUTES: Routes = [ 
    { path: '', component: MyComponent, canDeactivate: [PendingChangesGuard] }, 
]; 

Modulo:

import {PendingChangesGuard} from './pending-changes.guard'; 

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

NOTA: Come @JasperRisseeuw sottolineato, IE e Bordo gestire l'evento beforeunload in modo diverso da altri browser e includerà la parola false nella finestra di conferma quando l'evento beforeunload si attiva (ad esempio, il browser si aggiorna, chiude la finestra, ecc.). La navigazione all'interno dell'app Angular non viene alterata e mostrerà correttamente il messaggio di avviso di conferma designato. Coloro che hanno bisogno di supportare IE/Edge e non vogliono che false mostri/desideri un messaggio più dettagliato nella finestra di conferma quando l'evento beforeunload si attiva potrebbero anche voler vedere la risposta di @ JasperRisseeuw per una soluzione alternativa.

+2

Questo funziona davvero bene @stewdebaker! Ho un'aggiunta a questa soluzione, vedi la mia risposta qui sotto. –

+1

importare {Observable} da 'rxjs/Observable'; Manca ComponentCanDeactivate –

+1

Ho dovuto aggiungere '@Injectable()' alla classe PendingChangesGuard. Inoltre, ho dovuto aggiungere PendingChangesGuard ai miei provider in '@ NgModule' – spottedmahn

33

L'esempio con @Hostlistener di stewdebaker funziona molto bene, ma ho apportato una modifica in più perché IE e Edge visualizzano il "falso" che viene restituito dal metodo canDeactivate() sulla classe MyComponent all'utente finale .

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

Buona cattura @JasperRisseeuw! Non mi rendevo conto che IE/Edge gestiva diversamente. Questa è una soluzione molto utile per coloro che hanno bisogno di supportare IE/Edge e non vogliono che 'false' venga mostrato nella finestra di conferma. Ho apportato una piccola modifica alla tua risposta per includere l''$' evento 'nell'annotazione '@ HostListener', poiché è necessario per poterlo accedere nella funzione' unloadNotification'. – stewdebaker

+1

Grazie, ho dimenticato di copiare ", ['$ event']" dal mio codice così bene anche da te! –

+0

l'unica soluzione che il lavoro è stato questo (usando Edge). tutti gli altri funzionano ma mostra solo un messaggio di dialogo predefinito (Chrome/Firefox), non il mio testo ... Persino [ho fatto una domanda] (http: // StackOverflow.it/questions/42142053/candeactivate-confirm-message) per capire cosa sta succedendo –

0

La soluzione è stata più facile del previsto, non utilizzare href perché questo non è gestita da Angular Routing utilizzare routerLink direttiva, invece.

Problemi correlati