पृष्ठ छोड़ने से पहले सहेजे न गए परिवर्तनों के उपयोगकर्ता को चेतावनी दें


112

मैं अपने कोणीय 2 ऐप के किसी विशेष पृष्ठ को छोड़ने से पहले उपयोगकर्ताओं को बिना सहेजे परिवर्तन के बारे में चेतावनी देना चाहूंगा। आम तौर पर मैं उपयोग window.onbeforeunloadकरता हूं , लेकिन यह एकल पृष्ठ अनुप्रयोगों के लिए काम नहीं करता है।

मैंने पाया है कि कोणीय 1 में, आप उपयोगकर्ता के लिए $locationChangeStartएक confirmबॉक्स को फेंकने के लिए घटना में हुक कर सकते हैं , लेकिन मैंने ऐसा कुछ भी नहीं देखा है जो दिखाता है कि यह कोणीय 2 के लिए कैसे काम करना है, या यदि वह घटना अभी भी मौजूद है । मैंने ag1 के लिए प्लगइन्स भी देखे हैं जो कि कार्यक्षमता प्रदान करते हैं onbeforeunload, लेकिन फिर से, मैंने ag2 के लिए इसका उपयोग करने का कोई तरीका नहीं देखा है।

मुझे उम्मीद है कि किसी और ने इस समस्या का हल ढूंढ लिया है; या तो विधि मेरे उद्देश्यों के लिए ठीक काम करेगी।


2
जब आप पृष्ठ / टैब बंद करने का प्रयास करते हैं तो यह एकल पृष्ठ अनुप्रयोगों के लिए काम करता है। इसलिए प्रश्न का कोई भी उत्तर केवल एक आंशिक समाधान होगा यदि वे उस तथ्य की अनदेखी करते हैं।
9ilsdx 9rvj 0lo

जवाबों:


74

राउटर एक जीवनचक्र कॉलबैक 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.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]
  }])
);

28
ओपी ने जो मांगा, उसके विपरीत, CanDeactivate -आवर्ती रूप से- ऑनबॉर्फनोड घटना (दुर्भाग्य से) पर हुकिंग नहीं है। मतलब कि अगर उपयोगकर्ता किसी बाहरी URL पर जाने की कोशिश करता है, तो विंडो बंद करें, आदि CanDeactivate को ट्रिगर नहीं किया जाएगा। यह केवल तभी काम करता है जब उपयोगकर्ता ऐप के भीतर रह रहा हो।
क्रिस्टोफ विडाल

1
@ChristopheVidal सही है। कृपया एक समाधान के लिए मेरा उत्तर देखें जो किसी बाहरी URL पर नेविगेट करने, विंडो को बंद करने, पृष्ठ को फिर से लोड करने आदि को शामिल करता है
stewdebaker

मार्गों को बदलते समय यह काम करता है। क्या होगा अगर यह एसपीए है? इसे हासिल करने का कोई और तरीका?
सुजय कोडमाला

stackoverflow.com/questions/36763141/… आपको मार्गों के साथ भी इसकी आवश्यकता होगी। यदि विंडो बंद है या वर्तमान साइट से दूर नेविगेट की गई है तो canDeactivateकाम नहीं करेगा।
गुंटर ज़ोचबॉयर

214

ब्राउज़र रिफ्रेश के विरूद्ध गार्ड को कवर करने के लिए, खिड़की बंद करना, आदि (देखें इस मुद्दे पर विवरण के लिए गुंटर के जवाब में @ क्रिस्टोफ़ेविडल की टिप्पणी), मुझे @HostListenerआपकी कक्षा के canDeactivateकार्यान्वयन के लिए डेकोरेटर को जोड़ना मददगार लगा है ।beforeunload window इवेंट के । सही तरीके से कॉन्फ़िगर किए जाने पर, यह एक ही समय में इन-ऐप और बाहरी नेविगेशन दोनों के खिलाफ रक्षा करेगा।

उदाहरण के लिए:

घटक:

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 और एज beforeunloadइवेंट को अन्य ब्राउज़रों से अलग तरीके से हैंडल करते हैं और falseजब यह beforeunloadइवेंट सक्रिय हो जाता है (जैसे, ब्राउज़र रीफ्रेश, विंडो को बंद करना, आदि) को कन्फर्मेशन डायलॉग में शामिल करेगा । कोणीय ऐप के भीतर नेविगेट करना अप्रभावित है और आपके निर्दिष्ट पुष्टि चेतावनी संदेश को ठीक से दिखाएगा। जिन लोगों को आईई / एज का समर्थन करने की आवश्यकता होती है और falseजब beforeunloadघटना सक्रिय हो जाती है, तो पुष्टिकरण संवाद में एक अधिक विस्तृत संदेश दिखाना / नहीं करना चाहते / चाहती हैं कि वह वर्कअराउंड के लिए @ JasperRisseeuw का उत्तर भी देखना चाहें।


2
यह वास्तव में अच्छी तरह से काम करता है @stewdebaker! मेरे पास इस समाधान का एक जोड़ है, नीचे मेरा उत्तर देखें।
जैस्पर रिसीसुव

1
'rxjs / Observable' से आयात {ऑब्जर्वेबल}; से गायब है ComponentCanDeactivate
महमूद अली Kassem

1
मुझे @Injectable()PendingChangesGuard वर्ग में जोड़ना था । इसके अलावा, मुझे अपने प्रदाताओं में PendingChangesGuard को भी जोड़ना पड़ा@NgModule
spottedmahn

मुझे जोड़ना थाimport { HostListener } from '@angular/core';
fidev

4
यह ध्यान देने योग्य है कि यदि आप के साथ दूर किया जा रहा है तो आपको एक बूलियन वापस करना होगा beforeunload। यदि आप एक अवलोकन करते हैं, तो यह काम नहीं करेगा। आप की तरह कुछ के लिए अपने इंटरफेस को बदलने के लिए चाहते हो सकता है canDeactivate: (internalNavigation: true | undefined)इस तरह और अपने घटक कहते हैं: return component.canDeactivate(true)। इस तरह, आप देख सकते हैं कि आप falseप्रेक्षण योग्य के बजाय आंतरिक रूप से वापस जाने के लिए नेविगेट नहीं कर रहे हैं ।
jsgoupil

58

Stewdebaker से @Hostlistener के साथ उदाहरण वास्तव में अच्छी तरह से काम करता है, लेकिन मैंने इसमें एक और बदलाव किया है क्योंकि IE और एज "झूठी" प्रदर्शित करते हैं जो कि MyDponent class () के द्वारा MyComponent class द्वारा अंतिम उपयोगकर्ता को लौटा दी जाती है।

घटक:

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
अच्छा कैच @JasperRisseeuw! मुझे एहसास नहीं था कि IE / Edge ने इसे अलग तरह से संभाला है। यह उन लोगों के लिए एक बहुत ही उपयोगी उपाय है जिन्हें IE / Edge का समर्थन करने की आवश्यकता है और falseपुष्टि डायलॉग में नहीं दिखाना चाहते हैं । मैंने एनोटेशन '$event'में शामिल करने के लिए आपके उत्तर के लिए एक छोटा सा संपादन किया @HostListener, क्योंकि इसे unloadNotificationफ़ंक्शन में उपयोग करने में सक्षम होने के लिए आवश्यक है ।
स्टूडेबकर

1
धन्यवाद, मैं कॉपी करना भूल गया ", ['$ घटना']" मेरे अपने कोड से आप से भी इतनी अच्छी पकड़!
जैस्पर रिसीसुव

एकमात्र समाधान काम करता था यह एक (एज का उपयोग करके) था। अन्य सभी काम करते हैं, लेकिन केवल डिफ़ॉल्ट संवाद संदेश (क्रोम / फ़ायरफ़ॉक्स) दिखाता है, मेरा पाठ नहीं ... मैंने एक सवाल भी पूछा कि यह क्या हो रहा है
एल्मर दंतास

@ElmerDantas क्रोम / फ़ायरफ़ॉक्स में डिफ़ॉल्ट संवाद संदेश क्यों दिखाता है, इसकी व्याख्या के लिए कृपया मेरे प्रश्न का उत्तर देखें ।
स्टूडेबकर

2
वास्तव में, यह काम करता है, क्षमा करें! मुझे मॉड्यूल प्रदाताओं में गार्ड का संदर्भ देना था।
tudor.iliescu

6

मैंने @stewdebaker से समाधान लागू किया है जो वास्तव में अच्छी तरह से काम करता है, हालाँकि मैं क्लंकी मानक जावास्क्रिप्ट पुष्टि के बजाय एक अच्छा बूटस्ट्रैप पॉपअप चाहता था। मान लें कि आप पहले से ही एनएक्सएक्स-बूटस्ट्रैप का उपयोग कर रहे हैं, तो आप @ stwedebaker के समाधान का उपयोग कर सकते हैं, लेकिन जो मैं यहां दिखा रहा हूं, उसके लिए 'गार्ड' स्वैप करें। आपको ngx-bootstrap/modalएक नया परिचय देना और जोड़ना भी होगाConfirmationComponent :

रक्षक

('पुष्टिकरण' को एक ऐसे फ़ंक्शन से बदलें जो बूटस्ट्रैप मोडल खोलेगा - एक नया, कस्टम प्रदर्शित करना 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();
    }
}

और चूंकि नया एक html टेम्पलेट में ConfirmationComponentउपयोग किए बिना प्रदर्शित किया जाएगा selector, इसलिए इसे entryComponentsआपके रूट में घोषित किया जाना चाहिए app.module.ts(या जो भी आप अपने कंप्यूटर मॉड्यूल का नाम देते हैं)। निम्नलिखित परिवर्तन करें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
क्या ब्राउज़र रिफ्रेश के लिए कस्टम मॉडल दिखाने का कोई मौका है?
k11k2

एक रास्ता होना चाहिए, हालांकि यह समाधान मेरी जरूरतों के लिए ठीक था। अगर मेरे पास समय है तो मैं चीजों को और विकसित करूंगा, हालांकि मैं इस जवाब को काफी समय तक अपडेट नहीं कर पाऊंगा!
क्रिस हैल्क्रो

1

समाधान अपेक्षा से अधिक आसान था, इसका उपयोग न करें hrefक्योंकि routerLinkइसके बजाय कोणीय रूटिंग द्वारा निर्देश का उपयोग नहीं किया जाता है ।


1

जून 2020 उत्तर:

ध्यान दें कि इस बिंदु पर प्रस्तावित सभी समाधान एंगुलर के साथ एक महत्वपूर्ण ज्ञात दोष से नहीं निपटते हैं canDeactivate गार्ड :

  1. उपयोगकर्ता ब्राउज़र, संवाद प्रदर्शित करता है, और उपयोगकर्ता CANCEL पर क्लिक करता है
  2. उपयोगकर्ता 'वापस' बटन पर फिर से क्लिक करता है, संवाद प्रदर्शित करता है, और उपयोगकर्ता क्लिक करता है CONFIRM पर
  3. नोट: उपयोगकर्ता को 2 बार वापस नेविगेट किया जाता है, जो उन्हें ऐप से पूरी तरह बाहर भी निकाल सकता है :(

इस पर यहाँ चर्चा की गई है , यहाँ , और यहाँ लंबाई पर


कृपया यहां प्रदर्शित समस्या का मेरा समाधान देखें जो सुरक्षित रूप से इस समस्या के काम करता है *। यह क्रोम, फ़ायरफ़ॉक्स और एज पर परीक्षण किया गया है।


* महत्वपूर्ण सीमा : इस स्तर पर, ऊपर बटन को क्लिक करने पर आगे का इतिहास साफ़ हो जाएगा, लेकिन पीछे के इतिहास को सुरक्षित रखें। यह समाधान उचित नहीं होगा यदि आपके आगे के इतिहास को संरक्षित करना महत्वपूर्ण है। मेरे मामले में, मैं आमतौर पर एक मास्टर-डिटेल रूटिंग रणनीति का उपयोग करता हूं जब यह रूपों में आता है, इसलिए आगे के इतिहास को बनाए रखना महत्वपूर्ण नहीं है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.