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

사용자가 내 Angular 2 앱의 특정 페이지를 떠나기 전에 저장되지 않은 변경 사항에 대해 경고하고 싶습니다. 일반적으로을 사용 window.onbeforeunload하지만 단일 페이지 응용 프로그램에서는 작동하지 않습니다.

앵귤러 1에서 $locationChangeStart이벤트에 연결 confirm하여 사용자를 위해 상자 를 던질 수 있지만 앵귤러 2에서이 작업을 수행하는 방법 또는 해당 이벤트가 여전히 존재하는지 보여주는 내용을 본 적이 없습니다. . 에 대한 기능을 제공하는 ag1 용 플러그인 도 보았지만 onbeforeunload다시 한 번, ag2에 대해 사용하는 방법을 보지 못했습니다.

다른 사람이이 문제에 대한 해결책을 찾았기를 바랍니다. 두 가지 방법 모두 내 목적에 적합합니다.



답변

라우터는 수명주기 콜백 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]
  }])
);

답변

또한 브라우저 새로 고침, 창 닫기 등을 방지하기 위해 (문제에 대한 자세한 내용은 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의 답변을 볼 수도 있습니다.


답변

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


답변

정말 잘 작동하는 @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]


답변

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


답변

2020 년 6 월 답변 :

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

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

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


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


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


답변