페이지를 떠나기 전에 저장되지 않은 변경 사항에 대해 사용자에게 경고


112

사용자가 내 Angular 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
OP가 요청한 것과 달리 CanDeactivate는 현재 onbeforeunload 이벤트에 연결되지 않습니다 (불행히도). 즉, 사용자가 외부 URL로 이동하려고하면 창을 닫는 등 CanDeactivate가 트리거되지 않습니다. 사용자가 앱 내에있을 때만 작동하는 것 같습니다.
Christophe Vidal

1
@ChristopheVidal이 맞습니다. 외부 URL로 이동, 창 닫기, 페이지 다시로드 등을 포함하는 솔루션에 대한 내 답변을 참조하십시오.
stewdebaker

이것은 경로를 변경할 때 작동합니다. SPA라면 어떨까요? 이것을 달성하는 다른 방법은 무엇입니까?
sujay kodamala

stackoverflow.com/questions/36763141/… 경로도 필요합니다. 창이 닫히거나 현재 사이트에서 벗어나면 canDeactivate작동하지 않습니다.
Günter Zöchbauer

214

또한 브라우저 새로 고침, 창 닫기 등을 방지하기 위해 (문제에 대한 자세한 내용은 Günter의 답변에 대한 @ChristopheVidal의 의견 참조) 이벤트 를 수신하기 위해 @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와 Edge beforeunload는 다른 브라우저와 다르게 이벤트를 처리하며 이벤트가 활성화 false될 때 확인 대화 상자에 단어 를 포함합니다 beforeunload(예 : 브라우저 새로 고침, 창 닫기 등). Angular 앱 내에서 탐색하는 것은 영향을받지 않으며 지정된 확인 경고 메시지가 올바르게 표시됩니다. IE / Edge를 지원해야 false하고 beforeunload이벤트가 활성화 될 때 확인 대화 상자에 더 자세한 메시지를 표시하거나 원하지 않는 사람들 은 해결 방법에 대한 @JasperRisseeuw의 답변을 볼 수도 있습니다.


2
@stewdebaker 정말 잘 작동합니다! 이 솔루션에 하나의 추가 사항이 있습니다.
Jasper Risseeuw

1
import {Observable} from 'rxjs / Observable'; ComponentCanDeactivate에서 누락
마흐무드 알리 카셈 (이집트)에게

1
@Injectable()PendingChangesGuard 클래스 에 추가 해야했습니다. 또한 PendingChangesGuard를 공급자에 추가해야했습니다@NgModule
spottedmahn

나는 추가했다import { HostListener } from '@angular/core';
fidev

4
을 (를) 사용하여 이동하는 경우 부울을 반환해야한다는 점에 주목할 가치가 있습니다 beforeunload. Observable을 반환하면 작동하지 않습니다. 다음과 같이 인터페이스를 변경 canDeactivate: (internalNavigation: true | undefined)하고 구성 요소 를 다음과 같이 호출 할 수 return component.canDeactivate(true)있습니다.. 이렇게 false하면 Observable 대신 반환하기 위해 내부적으로 이동하지 않는지 확인할 수 있습니다 .
jsgoupil

58

stewdebaker의 @Hostlistener를 사용한 예제는 정말 잘 작동하지만 IE와 Edge가 MyComponent 클래스의 canDeactivate () 메서드가 최종 사용자에게 반환하는 "false"를 표시하기 때문에 한 가지 더 변경했습니다.

구성 요소:

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기능.
stewdebaker

1
감사합니다. 제 코드에서 ", [ '$ event']"를 복사하는 것을 잊었습니다. 여러분도 잘 받았습니다!
Jasper Risseeuw

작품의 유일한 해결책은 이것입니다 (Edge 사용). 다른 모든 작품 만 공연 대화 메시지 (크롬 / 파이어 폭스) 기본은 나의 텍스트는 ... 난 질문을 무슨 일이 일어나고 있는지 이해하기
엘머 Dantas

@ElmerDantas 기본 대화 상자 메시지가 Chrome / Firefox에 표시되는 이유에 대한 설명은 질문에 대한 내 답변 을 참조하십시오 .
stewdebaker

2
실제로 작동합니다, 죄송합니다! 모듈 공급자에서 가드를 참조해야했습니다.
tudor.iliescu

6

정말 잘 작동하는 @stewdebaker의 솔루션을 구현했지만, 어설픈 표준 JavaScript 확인 ​​대신 멋진 부트 스트랩 팝업을 원했습니다. 이미 ngx-bootstrap을 사용하고 있다고 가정하면 @stwedebaker의 솔루션을 사용할 수 있지만 여기에 표시되는 '가드'를 교체하십시오. 또한을 소개 ngx-bootstrap/modal하고 다음을 새로 추가 해야합니다 ConfirmationComponent.

가드

( 'confirm'을 부트 스트랩 모달을 여는 함수로 바꾸십시오-새로운 custom 표시 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;
    })
  }
}

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

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

그리고 새로운 것은 html 템플릿에서 ConfirmationComponent를 사용하지 않고 표시 되기 때문에 루트 (또는 루트 모듈의 이름을 무엇이든) 에서 selector선언해야합니다 . 다음과 같이 변경하십시오 .entryComponentsapp.module.tsapp.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

이 솔루션이 내 필요에 적합했지만 방법이 있어야합니다. 시간이 지남에 따라 더 발전 할 수 있지만 잠시 동안이 답변을 업데이트 할 수는 없지만 죄송합니다!
Chris Halcrow

1

솔루션은 예상보다 쉬웠으며 대신 hrefAngular Routing 사용 routerLink지시문에 의해 처리 되지 않으므로 사용하지 마십시오 .


1

2020 년 6 월 답변 :

이 시점까지 제안 된 모든 솔루션은 Angular의 canDeactivate가드에 대해 알려진 중대한 결함을 처리하지 않습니다 .

  1. 사용자가 브라우저에서 '뒤로'버튼을 클릭하면 대화 상자가 표시되고 사용자가 취소를 클릭 합니다.
  2. 사용자가 '뒤로'버튼을 다시 클릭하고 대화 상자가 표시되고 사용자가 CONFIRM을 클릭 합니다.
  3. 참고 : 사용자가 2 번 뒤로 이동하면 앱에서 모두 제거 할 수도 있습니다.

이것은 여기 , 여기 , 그리고 여기 에서 길게 논의되었습니다.


이 문제를 안전하게 해결 하는 여기에 설명 된 문제에 대한 내 솔루션을 참조하십시오 *. 이것은 Chrome, Firefox 및 Edge에서 테스트되었습니다.


* 중요주의 사항 :이 단계에서 위의 내용은 뒤로 버튼을 클릭하면 앞으로 이력을 지우지 만 뒤로 이력은 보존합니다. 이 솔루션은 앞으로 이력을 보존해야하는 경우 적절하지 않습니다. 필자의 경우 양식과 관련하여 일반적으로 마스터-디테일 라우팅 전략을 사용 하므로 전달 기록을 유지하는 것은 중요하지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.