Sayfadan ayrılmadan önce kullanıcıyı kaydedilmemiş değişiklikler konusunda uyarın


112

Açısal 2 uygulamamın belirli bir sayfasından ayrılmadan önce kullanıcıları kaydedilmemiş değişiklikler konusunda uyarmak istiyorum. Normalde kullanırdım window.onbeforeunload, ancak bu tek sayfalı uygulamalar için çalışmaz.

Açısal 1'de, kullanıcı için $locationChangeStartbir confirmkutu atmak için olaya bağlanabileceğinizi buldum , ancak bunun açısal 2 için nasıl çalıştırılacağını gösteren bir şey görmedim veya bu olay hala mevcutsa . Ayrıca ag1 için işlevsellik sağlayan eklentiler de gördüm onbeforeunload, ancak bunu ag2 için kullanmanın herhangi bir yolunu görmedim.

Umarım bir başkası bu soruna bir çözüm bulmuştur; her iki yöntem de amaçlarım için iyi çalışacaktır.


2
Sayfayı / sekmeyi kapatmaya çalıştığınızda, tek sayfalı uygulamalar için çalışır. Yani soruya verilecek herhangi bir cevap, bu gerçeği görmezden gelirlerse, yalnızca kısmi bir çözüm olacaktır.
9ilsdx 9rvj 0lo

Yanıtlar:


74

Yönlendirici, CanDeactivate için bir yaşam döngüsü geri araması sağlar

daha fazla ayrıntı için koruma eğitimine bakın

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

orijinal (RC.x yönlendirici)

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

29
OP'nin istediğinin aksine, CanDeactivate -şu anda- onbeforeunload olayına bağlanmıyor (maalesef). Yani, kullanıcı harici bir URL'ye gitmeye çalışırsa, pencereyi kapatın, vb. CanDeactivate tetiklenmeyecektir. Yalnızca kullanıcı uygulama içinde kaldığında çalışıyor gibi görünüyor.
Christophe Vidal

1
@ChristopheVidal doğru. Lütfen harici bir URL'ye
gitmeyi

Bu, rotaları değiştirirken çalışır. Ya SPA ise? Bunu başarmanın başka bir yolu var mı?
sujay kodamala

stackoverflow.com/questions/36763141/… Buna rotalar için de ihtiyaç duyarsınız. Pencere kapatılırsa veya mevcut siteden uzaklaşılırsa canDeactivateçalışmaz.
Günter Zöchbauer

214

Ayrıca tarayıcı yenilemelerine, pencerenin kapatılmasına vb. Karşı korumaları da kapsamak için (sorunla ilgili ayrıntılar için @ ChristopheVidal'ın Günter'in cevabına yaptığı açıklamaya bakın), @HostListenerdekoratörün sınıfınızın canDeactivateuygulamasına eklenmesinin yararlı olduğunu buldum .beforeunload window etkinliği . Doğru yapılandırıldığında, bu aynı anda hem uygulama içi hem de harici navigasyona karşı koruma sağlayacaktır.

Örneğin:

Bileşen:

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

Muhafız:

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

Rotalar:

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

Modül:

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

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

NOT : @JasperRisseeuw'un belirttiği gibi, IE ve Edge, beforeunloadolayı diğer tarayıcılardan farklı şekilde ele alır falseve beforeunloadolay etkinleştirildiğinde (örn. Tarayıcı yenilenir, pencereyi kapatır, vb.) Onaylama iletişim kutusuna kelimeyi ekler . Angular uygulaması içinde uzaklaşmak etkilenmez ve belirlenmiş onay uyarı mesajınızı doğru şekilde gösterir. IE / Edge'i desteklemesi gereken falseve beforeunloadolay etkinleştirildiğinde onaylama iletişim kutusunda daha ayrıntılı bir mesaj göstermek istemeyen / istemeyenler de @ JasperRisseeuw'un geçici çözüm yanıtını görmek isteyebilir.


2
Bu gerçekten iyi çalışıyor @ stewdebaker! Bu çözüme bir ekim var, aşağıdaki cevabıma bakın.
Jasper Risseeuw

1
{Observable} dosyasını 'rxjs / Observable'dan içe aktar; ComponentCanDeactivate
Mahmoud Ali Kassem'de

1
@Injectable()PendingChangesGuard sınıfına eklemem gerekiyordu. Ayrıca, sağlayıcılarıma @NgModule
PendingChangesGuard'ı

Eklemek zorunda kaldımimport { HostListener } from '@angular/core';
fidev

4
Eğer başka bir yere giderseniz diye bir boole döndürmeniz gerektiğini fark etmeye değer beforeunload. Bir Gözlemlenebilir'i iade ederseniz, işe yaramaz. Sen böyle bir şey için arayüzünü değiştirmek isteyebilirsiniz canDeactivate: (internalNavigation: true | undefined), bu gibi bileşen ve çağrı: return component.canDeactivate(true). Bu şekilde, falsebir Gözlemlenebilir yerine geri dönmek için dahili olarak uzaklaşıp gitmediğinizi kontrol edebilirsiniz.
jsgoupil

58

Stewdebaker'dan @Hostlistener örneği gerçekten iyi çalışıyor, ancak bir değişiklik daha yaptım çünkü IE ve Edge, MyComponent sınıfında canDeactivate () yöntemi tarafından son kullanıcıya döndürülen "false" öğesini gösteriyor.

Bileşen:

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
İyi yakalamak @JasperRisseeuw! IE / Edge'in bunu farklı şekilde ele aldığını bilmiyordum. Bu, IE / Edge'i desteklemesi gereken falseve onaylama iletişim kutusunda gösterilmesini istemeyenler için çok kullanışlı bir çözümdür . İşleve erişebilmek için gerekli olduğundan '$event', @HostListeneraçıklamaya eklemek için cevabınızda küçük bir düzenleme yaptım unloadNotification.
stewdebaker

1
Teşekkürler, ", ['$ event']" kodunu kendi kodumdan kopyalamayı unuttum, sizden de çok iyi yakaladım!
Jasper Risseeuw

işlerin tek çözümü bu (Edge kullanarak) oldu. diğerleri çalışıyor, ancak yalnızca varsayılan iletişim mesajını gösteriyor (Chrome / Firefox), metnimi değil ... Ne olduğunu anlamak için bir soru bile sordum
Elmer Dantas

@ElmerDantas , Chrome / Firefox'ta varsayılan iletişim mesajının neden gösterildiğine dair bir açıklama için lütfen sorunuza verdiğim yanıta bakın .
stewdebaker

2
Aslında işe yarıyor, üzgünüm! Modül sağlayıcılarındaki korumaya başvurmak zorunda kaldım.
tudor.iliescu

6

@ Stewdebaker'ın çözümünü gerçekten iyi bir şekilde uyguladım, ancak hantal standart JavaScript onayı yerine güzel bir önyükleme açılır penceresi istedim. Halihazırda ngx-bootstrap kullandığınızı varsayarsak, @ stwedebaker'ın çözümünü kullanabilirsiniz, ancak burada gösterdiğimle 'Guard'ı değiştirebilirsiniz. Ayrıca şunları tanıtmanız ngx-bootstrap/modalve yenisini eklemeniz gerekir ConfirmationComponent:

Muhafız

("onayla" yı bir önyükleme modeli açacak bir işlevle değiştirin - yeni, özel görüntüleyen 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>

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

Ve yeni ConfirmationComponent, selectorbir html şablonunda a kullanılmadan görüntüleneceğinden entryComponents, kökünüzde app.module.ts(veya kök modülünüze ne isim verirseniz) bildirilmesi gerekir. Aşağıdaki değişiklikleri yapın 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]

1
tarayıcı yenilemesi için özel model gösterme şansı var mı?
k11k2

Bu çözüm ihtiyaçlarım için uygun olmasına rağmen bir yolu olmalı. Zaman yaparsam işleri daha da geliştireceğim, ancak bu yanıtı bir süre daha güncelleyemeyeceğim, üzgünüm!
Chris Halcrow

1

Çözüm beklenenden daha kolaydı, kullanmayın hrefçünkü routerLinkbunun yerine Angular Routing kullanım yönergesi tarafından ele alınmaz .


1

Haziran 2020 yanıtı:

Bu noktaya kadar önerilen tüm çözümlerin Angular'ın canDeactivatekorumalarında önemli bir bilinen kusurla ilgilenmediğini unutmayın :

  1. Kullanıcı tarayıcıdaki 'geri' düğmesini tıklar, iletişim kutusu görüntülenir ve kullanıcı İPTAL'i tıklar .
  2. Kullanıcı 'geri' düğmesine tekrar tıklar, iletişim kutusu görüntülenir ve kullanıcı ONAYLA'ya tıklar .
  3. Not: Kullanıcı 2 kez geri gider ve bu da onları uygulamadan tamamen çıkarabilir :(

Bu tartışılmıştır burada , burada , ve uzunluğu da burada


Lütfen burada gösterilen soruna yönelik bu soruna güvenle çözüm getiren çözümüme bakın *. Bu, Chrome, Firefox ve Edge'de test edilmiştir.


* ÖNEMLİ BOŞLUK : Bu aşamada, geri düğmesine tıklandığında yukarıdakiler ileri geçmişi temizler, ancak geri geçmişi korur. İleri geçmişinizi korumak hayati önem taşıyorsa, bu çözüm uygun olmayacaktır. Benim durumumda, formlar söz konusu olduğunda genellikle bir ana ayrıntılı yönlendirme stratejisi kullanırım, bu nedenle ileriye dönük geçmişi korumak önemli değildir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.